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
nevertype is used to catch missing cases. assertNeveris a common helper function.- This pattern is great with discriminated unions and
switch. - It makes refactoring safer.
Next up, we start classes with Classes →.