The never Type & Exhaustiveness Checking
Quick Overview
`never` is the type with no possible value, so nothing is assignable to it. Assigning the narrowed variable to `never` in a default branch turns a forgotten union member into a compile error.
Deep Dive
1. The Impossible Type
In TypeScript's type theory, never represents the type of values that can never occur.
2. Where never Appears Automatically
never is automatically inferred as the return type for functions that throw errors indefinitely or contain infinite loops:
// Infered return type: never
function throwError(message: string): never {
throw new Error(message);
}
function infiniteLoop(): never {
while (true) {}
}
3. Exhaustiveness Checking Pattern
The most powerful application of never is Exhaustiveness Checking inside switch statements. By assigning an unhandled union case to a variable of type never, TypeScript will throw a compile-time error whenever a new union case is added without updating the switch statement!
type Shape =
| { kind: "circle"; radius: number }
| { kind: "square"; side: number }
| { kind: "triangle"; base: number; height: number }; // Newly added shape!
function calculateArea(shape: Shape): number {
switch (shape.kind) {
case "circle":
return Math.PI * shape.radius ** 2;
case "square":
return shape.side * shape.side;
case "triangle":
return 0.5 * shape.base * shape.height;
default: {
// Exhaustiveness check!
// If a case is missing, shape will NOT be 'never', triggering a compile error!
const _exhaustiveCheck: never = shape;
return _exhaustiveCheck;
}
}
}
4. Visual Exhaustive Validation
[ Union Member Added ] ──> "triangle"
│
▼
[ Default Switch Branch ] ──> Assigned to 'never' -> Compile Error: Type 'triangle' not assignable to 'never'!
5. Common Mistakes
- Omitting Exhaustive Default Checks: Without assigning unhandled cases to
never, adding new union members silently fails at runtime without compile warnings!
6. Next Steps
Use exhaustiveness checks on all discriminated unions. Next, let me explain Index Signatures and Mapped Types!
Next Up in TypeScript & Type System Mechanics
TypeScript Primitives & Explicit Type Annotations
TypeScript erases entirely at build time, so a `string` annotation produces no runtime check whatsoever. A value arriving from an API is whatever the server actually sent, regardless of what the annotation claims.
Type Inference & Literal Types (const vs. let)
`let x = "GET"` infers `string` because the binding can change; `const x = "GET"` infers the literal `"GET"`. That widening rule is why object properties need `as const` to stay narrow.
Arrays, Tuples & Readonly Modifiers
`string[]` is any number of strings; `[string, number]` is exactly two, in that order. `readonly` removes the mutating methods at compile time, so `push` becomes a type error rather than a runtime surprise.
