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.
In TypeScript's type theory, never represents the type of values that can never occur.
never Appears Automaticallynever 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) {}
}
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;
}
}
}
[ Union Member Added ] ──> "triangle"
│
▼
[ Default Switch Branch ] ──> Assigned to 'never' -> Compile Error: Type 'triangle' not assignable to 'never'!
never, adding new union members silently fails at runtime without compile warnings!Use exhaustiveness checks on all discriminated unions. Next, let me explain Index Signatures and Mapped Types!
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.
`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.
`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.