AZ.dev

Make a switch fail to compile when a union case is missing

| typescript: 2.0+

Assign the narrowed value to never in the default branch. Adding a member to the union then breaks the build at every switch that doesn’t handle it.

type Shape = { kind: "circle"; r: number } | { kind: "square"; side: number };

function area(shape: Shape): number {
  switch (shape.kind) {
    case "circle":
      return Math.PI * shape.r ** 2;
    case "square":
      return shape.side ** 2;
    default: {
      const unreachable: never = shape;
      return unreachable;
    }
  }
}

Add { kind: "triangle"; ... } to Shape and the assignment errors with Type '{ kind: "triangle"; ... }' is not assignable to type 'never', naming the case you forgot.

Why it works

Each case narrows shape. By default, the compiler has removed every handled member, and what’s left is never only if nothing remains. A leftover member can’t be assigned to never, so the error appears exactly when the switch stops being exhaustive.

Reuse it as a helper

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

default:
  return assertNever(shape);

The throw covers the runtime case where a value from outside the type system (an API response, a database row) carries a kind the code never saw.

Gotchas

never exhaustive union switch narrowing

This entry is one card on a printable cheat sheet:

TypeScript Essentials Cheat Sheet

Was this helpful?

Related Entries

typescript-eslint --fix can break Prisma JSON column writesMake array and record lookups admit they can be undefinedCheck an object against a type without losing its literal types