← 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.

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"));

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.