TypeScript Best Practices

Chapter 2 · Practice 3 of 6

Make switches exhaustive with a never check

From Modeling data with types

Why it matters

When you add a new variant to a union, every switch that handles it should fail to compile until it handles the new case. TypeScript can do this for you: after all cases are handled, the value's type narrows to never. Assigning it to a never variable in the default branch is an error the moment a case is missing.

The handbook calls this exhaustiveness checking. It turns "remember to update all the switches" into a compiler error that lists each place to change.

Do

  • End every switch over a union with a default that assigns to never
  • Keep the helper in a shared utility so the pattern is consistent

Avoid

  • A default branch that silently returns a fallback for unknown variants

Example

TypeScript
function assertNever(value: never): never {
  throw new Error(`Unhandled case: ${JSON.stringify(value)}`);
}

function icon(state: RequestState<unknown>): string {
  switch (state.status) {
    case 'idle': return 'circle';
    case 'loading': return 'spinner';
    case 'success': return 'check';
    case 'error': return 'alert';
    default: return assertNever(state); // error here if a status is added
  }
}