ESC

Type to search the knowledge base.

Type Narrowing

How TypeScript refines types through control flow — predicates, discriminants, and patterns that keep strict mode honest.

intermediate2 min read
  • typescript
  • narrowing
  • types

Strict TypeScript is only useful if you narrow unions instead of asserting them away. Narrowing is how control flow becomes type safety.

Built-in guards

function len(x: string | string[]) {
  if (typeof x === 'string') return x.length;
  return x.length; // string[]
}

function process(x: string | null) {
  if (x == null) return;
  // x is string
  console.log(x.toUpperCase());
}

typeof, instanceof, equality checks, and in all narrow.

Discriminated unions

Model state machines explicitly:

type RequestState<T> =
  | { status: 'idle' }
  | { status: 'loading' }
  | { status: 'success'; data: T }
  | { status: 'error'; error: Error };

function view(state: RequestState<User>) {
  switch (state.status) {
    case 'idle':
      return 'Start';
    case 'loading':
      return '…';
    case 'success':
      return state.data.name;
    case 'error':
      return state.error.message;
  }
}

The discriminant (status) is the key. Avoid optional fields like data?: T; error?: Error for mutually exclusive states.

User-defined type predicates

function isFish(pet: Fish | Bird): pet is Fish {
  return (pet as Fish).swim !== undefined;
}

const pets: (Fish | Bird)[] = [];
pets.filter(isFish).forEach((f) => f.swim());

Prefer genuine runtime checks over as casts.

Assertion functions

function assertDefined<T>(x: T | null | undefined, msg: string): asserts x is T {
  if (x == null) throw new Error(msg);
}

After assertDefined(user), TypeScript treats user as defined.

What not to do

// Escaping the type system — last resort only
const el = document.querySelector('.app') as HTMLDivElement;

Prefer:

const el = document.querySelector('.app');
if (!(el instanceof HTMLDivElement)) throw new Error('missing .app');

Interview takeaway

Explain narrowing as control-flow analysis. Show a discriminated union for async UI state. Mention predicates when filters must preserve types.

Related guides