TypeScript Foundations·Types that pay for themselves

Narrowing in practice

Use small runtime checks so the compiler knows which type a value really is inside each branch.

Intermediate 12 min read Lesson 4 of 300 Challenge included
What you will be able to do
  • Use typeof to separate strings, numbers and booleans.
  • Use instanceof to tell which class an object came from.
  • Use the in check to see whether a property exists on an object.
  • Use a never helper so an exhaustive switch fails on missing cases.

In plain English

Narrowing means shrinking a union of types down to one type inside a block of code. The typeof check tells strings apart from numbers and other primitives, while instanceof tells you which class an object was built from and the in check tells you whether an object has a certain property. A discriminated union gives every branch a shared literal field such as kind so a switch can tell the branches apart. An exhaustive check with never makes the compiler warn you when you forget to handle a new case.

Step by step

  1. 1Check the value with typeof for primitives.
  2. 2Check the value with instanceof for class instances.
  3. 3Check the value with the in operator for an object property.
  4. 4Switch on a shared literal field for a discriminated union.
  5. 5Pass an impossible leftover to a never helper for exhaustiveness.
narrowing.tsTypeScript
class ApiError {
  status = 500
}

function describe(error: Error | ApiError): string {
  if (error instanceof ApiError) {
    return 'API error ' + error.status
  }
  return error.message
}

function format(value: string | number): string {
  if (typeof value === 'string') {
    return value.trim()
  }
  return value.toFixed(1)
}

type Event =
  | { kind: 'click'; x: number }
  | { kind: 'key'; key: string }

function handle(event: Event): string {
  switch (event.kind) {
    case 'click':
      return 'clicked at ' + event.x
    case 'key':
      return 'pressed ' + event.key
    default:
      return assertNever(event)
  }
}

function assertNever(value: never): never {
  throw new Error('Unknown event')
}

Common mistakes

  • Reading a property before narrowing causes an error because the compiler cannot prove it exists.
  • Casting with as to quiet an error hides the bug instead of fixing the check.
  • Forgetting the default branch in a switch means future union members slip through silently.
  • Using typeof to check for a class only returns object, which is why instanceof exists.
Your turn

Handle a result union

Medium

Write a function that takes a Result union with ok and error variants and returns a message for each, using an exhaustive switch with a never helper.

TYPESCRIPT · starter
type Result = unknown

function describe(result: Result): string {
  return ''
}
Check your understanding before moving on.
Knowledge checkQuestion 1

What does an exhaustive check with never guarantee?

Select an answer to continue
Study tools

Make it yours.

Keep a private note for this lesson, then turn the key ideas into flashcards you can revisit.

Sign in to save notes and flashcards.Both are private to your account and stay with your progress.
Sign in
Finished reading?

Lock in what you just learned.

Mark this lesson complete to save your place and move one step closer to the certificate.

Next: tsconfig for beginners
Lesson 4 of 300 · 0% of course