CodeWithMihir

TypeScript Tutorial

TypeScript Exhaustive Checks Explained

Learn how exhaustive checks work in TypeScript using never, switch statements, discriminated unions, and safer refactoring patterns.

Welcome back! I am Mihir, and in this lesson we will learn exhaustive checks in TypeScript.

An exhaustive check makes sure you handled every possible member of a union.


The Problem

type Status = "loading" | "success" | "error";

function getMessage(status: Status) {
  switch (status) {
    case "loading":
      return "Loading...";
    case "success":
      return "Success!";
  }
}

We forgot "error".

TypeScript may not always make this obvious unless we ask it to.


Using never for Exhaustive Checks

function assertNever(value: never): never {
  throw new Error(`Unexpected value: ${value}`);
}

never means no value should be able to reach this function.


Exhaustive switch Example

type Status = "loading" | "success" | "error";

function getMessage(status: Status) {
  switch (status) {
    case "loading":
      return "Loading...";
    case "success":
      return "Success!";
    case "error":
      return "Something went wrong";
    default:
      return assertNever(status);
  }
}

If every case is handled, status becomes never in the default block.


What Happens When You Add a New Case?

type Status = "loading" | "success" | "error" | "idle";

Now the switch is missing "idle".

The assertNever(status) line will show a TypeScript error because status is no longer never.

That is exactly what we want.


Exhaustive Checks with Discriminated Unions

type Shape =
  | { kind: "circle"; radius: number }
  | { kind: "square"; size: number }
  | { kind: "rectangle"; width: number; height: number };

function getArea(shape: Shape): number {
  switch (shape.kind) {
    case "circle":
      return Math.PI * shape.radius * shape.radius;
    case "square":
      return shape.size * shape.size;
    case "rectangle":
      return shape.width * shape.height;
    default:
      return assertNever(shape);
  }
}

If a new shape is added later, TypeScript helps you update this function.


Why Exhaustive Checks Are Useful

They protect you during refactoring.

When a union grows, TypeScript points to every place that needs to handle the new option.

This is especially helpful for:

  • app states
  • payment states
  • API response states
  • UI component variants

Quick Recap

  • Exhaustive checks ensure every union member is handled.
  • The never type is used to catch missing cases.
  • assertNever is a common helper function.
  • This pattern is great with discriminated unions and switch.
  • It makes refactoring safer.

Next up, we start classes with Classes →.