TypeScript Type Narrowing

Union types are how TypeScript models values that can be one of several things: string | null, Success | Failure, Circle | Square. You can't use a union's members directly. You first have to prove which case you're in. Narrowing is how TypeScript follows your runtime checks (if, switch, early returns) and refines the type in each branch.

Narrowing is what makes strictNullChecks practical and discriminated unions safe, and it lets you model state machines where impossible states can't be represented. It's also where many "why is this still string | undefined?" frustrations come from, so knowing the rules pays off daily.

TL;DR

Quick Example

A discriminated union for request state, handled exhaustively:

There's no way to read data while loading, or to have data and error at once. The type makes those states unrepresentable.

Core Concepts

Built-In Narrowing

Discriminated Unions

Give each variant a common property with a distinct literal type. Checking that property narrows to exactly one variant:

This is the most important modeling pattern in TypeScript. It's the typed equivalent of Rust's enums and Kotlin's sealed classes, and it's the standard shape for Redux actions, API results, and UI state.

Type Predicates

Wrap reusable checks in a function returning value is Type:

Since TS 5.5, simple predicates like x => x !== null are inferred automatically, so .filter(u => u !== null) already yields User[].

Assertion Functions

An assertion function throws if a condition fails, and narrows for the rest of the scope:

Exhaustiveness With never

After every variant is handled, the remaining type is never. Assigning it to a never variable (or passing it to an assertNever(x: never) helper) turns "forgot a case" into a compile error the moment someone adds a new variant.

Where Narrowing Stops

Best Practices

Model States as Discriminated Unions

Replace bags of optional fields ({ loading?: boolean; data?: T; error?: Error }) with a union of explicit states. You eliminate impossible combinations, and the compiler forces you to handle each state.

Prefer Narrowing to Assertions

x! and x as User tell the compiler to trust you, with no runtime check. An if or an assertion function checks at runtime and narrows. Reserve ! for cases where you've genuinely proven non-nullness in a way TypeScript can't follow.

Add an assertNever Helper

Use it in every default of a switch over a union. You get compile-time exhaustiveness and a clear runtime error if bad data arrives anyway.

Common Mistakes

Truthiness Narrowing That Drops Valid Values

Lying Type Predicates

The compiler trusts predicates completely. A wrong one spreads a false type through your codebase. Keep predicates thorough and tested, or generate them from a schema.

Using typeof for null and Arrays

typeof null === "object" and typeof [] === "object". Check x !== null and Array.isArray(x) explicitly.

FAQ

What's a discriminated union?

A union of object types that share one property, the discriminant, whose type is a different literal in each member (for example status: "loading" | "success" | "error"). Checking the discriminant narrows the value to a single member, so TypeScript knows exactly which other fields exist.

What's the difference between a type predicate and an assertion function?

A type predicate (x is T) returns a boolean; narrowing applies inside the if that uses it. An assertion function (asserts x is T) returns nothing and throws on failure; narrowing applies to all code after the call. Use predicates for branching and assertions for preconditions.

Why isn't my variable narrowed inside a callback?

Callbacks may run later, after the variable could have changed, so TypeScript discards narrowing for mutable let variables and object properties inside them. Assign the narrowed value to a const before the callback and use that.

How do I narrow unknown from JSON.parse?

With runtime checks (typeof, in, Array.isArray) wrapped in a type predicate, or, far more practically, with a schema library that validates and returns a typed value in one step.

Related Topics

References