Narrowing in practice
Use small runtime checks so the compiler knows which type a value really is inside each branch.
- 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
- 1Check the value with typeof for primitives.
- 2Check the value with instanceof for class instances.
- 3Check the value with the in operator for an object property.
- 4Switch on a shared literal field for a discriminated union.
- 5Pass an impossible leftover to a never helper for exhaustiveness.
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.
Handle a result union
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.
type Result = unknown
function describe(result: Result): string {
return ''
}What does an exhaustive check with never guarantee?
Make it yours.
Keep a private note for this lesson, then turn the key ideas into flashcards you can revisit.
Lock in what you just learned.
Mark this lesson complete to save your place and move one step closer to the certificate.