← Back to TypeScript Course | Chapter 8: Advanced Types | Lesson 7 of 20

Exhaustiveness Checking

Exhaustiveness checking ensures that every member of a union is handled. A common pattern is to use a never-returning helper in the default branch of a switch.
Syntax
typescript
function name(value: Union): returnType {
  switch (value.kind) {
    case "a": return /* ... */;
    default:
      const _exhaustive: never = value;
      return _exhaustive;
  }
}

The never Type

The never type represents values that can never actually occur, which means a function parameter typed as never can be used as a compile-time trap to detect any union member you forgot to handle.

Example: The never Type

typescript
function assertUnreachable(x: never): never {
  throw new Error("Unhandled case: " + x);
}
type Status = "active" | "inactive";
function check(s: Status) {
  if (s === "active") return "on";
  if (s === "inactive") return "off";
  return assertUnreachable(s);
}
console.log(check("active"));

Exhaustive Switch Statements

Discriminated unions are ideal for exhaustive switch statements, since each case in the switch can correspond to exactly one specific object variant identified by its discriminant property.

Example: Exhaustive Switch Statements

typescript
type Shape = { kind: "circle"; radius: number } | { kind: "square"; side: number };
function area(shape: Shape): number {
  switch (shape.kind) {
    case "circle": return Math.PI * shape.radius ** 2;
    case "square": return shape.side ** 2;
  }
}
console.log(area({ kind: "square", side: 3 }));

Exhaustive if Statements

Exhaustiveness can also be checked without a switch — narrow away every known union member with if/else branches, then pass whatever's left to a function typed to accept only never.

Example: Exhaustive if Statements

typescript
function assertNever(x: never): never {
  throw new Error("Unexpected: " + x);
}
type Status = "on" | "off";
function toggle(s: Status): Status {
  if (s === "on") return "off";
  if (s === "off") return "on";
  return assertNever(s);
}
console.log(toggle("on"));

Exhaustiveness in Functions

Returning never from what should be an impossible branch (like the default case of a switch) helps guarantee that a function genuinely handles every member of a union, not just the ones the author remembered.

Example: Exhaustiveness in Functions

typescript
type Status = "active" | "inactive";
function label(s: Status): string {
  switch (s) {
    case "active": return "Active";
    case "inactive": return "Inactive";
    default:
      const _exhaustive: never = s;
      return _exhaustive;
  }
}
console.log(label("active"));

Benefits of Exhaustiveness

Exhaustiveness checks make union-based code much safer as an application evolves — if a new member is added to the union later, the compiler immediately flags every switch or if-chain that needs updating.

Example: Benefits of Exhaustiveness

typescript
type Status = "active" | "inactive" | "pending";
function assertNever(x: never): never {
  throw new Error("Unhandled: " + x);
}
function label(s: Status): string {
  if (s === "active") return "Active";
  if (s === "inactive") return "Inactive";
  if (s === "pending") return "Pending";
  return assertNever(s);
}
console.log(label("pending"));
Related Topics
{# common_mistakes/chapter_summary/browser_support: on Hindi pages the view already swaps in the hi_ translation fields (or blanks these out if untranslated), so this renders correctly for both languages without a lang_code check here. #}
Common Mistakes
  1. Adding a new union member without updating the switch, and having a default that does nothing, so no error appears.
  2. Passing the wrong variable to assertUnreachable(x: never), such as the original union, which is not never.
  3. Forgetting that a missing default with never check only errors when a case is really unhandled, not when unrelated code changes.

Login to run this code

C/C++/Java/PHP execution requires a free account. Your code is saved — you'll land right back in the editor after logging in.