4.4 The never Type: Unreachable Code and Exhaustive Checks
Right, let’s talk about never. It’s the type that sounds the most dramatic and, frankly, a little bit emo. It’s the type system’s way of saying, “This path of code? Yeah, nothing is ever coming back from there. Not a value, not a Promise, not even undefined. It’s a black hole for program execution.”
You use never to represent two main scenarios: when a function never returns normally (it either throws an error or loops forever), and—far more importantly for your day-to-day code—to signal unreachable code for the sake of exhaustive type checking.
When Functions Never Return
Sometimes, a function’s job is to blow up your program gracefully or to just run until the heat death of the universe. These functions don’t return a value to the caller; they just… stop.
function throwError(message: string): never {
throw new Error(message);
}
function infiniteLoop(): never {
while (true) {
// do something forever. Enjoy your CPU fan noise.
}
}
If you tried to assign the result of these functions to a variable, TypeScript would rightly stop you. What are you going to store? Nothingness? The type checker isn’t a philosopher.
let myValue = throwError("Nope."); // TypeScript knows myValue is unassigned
The Real Power: Exhaustive Checks
This is where never becomes your best friend. It shines in switch statements and type guards where you want the compiler to guarantee we’ve handled every possible case. It’s like a built-in unit test for your control flow.
Imagine we’re dealing with a discriminated union. It’s a fancy term for an object that has a common property (the “discriminant”) that we can switch on.
type Shape =
| { kind: "circle"; radius: number }
| { kind: "square"; sideLength: number }
| { kind: "triangle"; base: number; height: number };
function getArea(shape: Shape): number {
switch (shape.kind) {
case "circle":
return Math.PI * shape.radius ** 2;
case "square":
return shape.sideLength ** 2;
case "triangle":
return (shape.base * shape.height) / 2;
}
}
This works. But what if a well-meaning (or malicious) colleague adds a new shape to the union, say { kind: "hexagon" }? Our getArea function would now silently return undefined for hexagons, which is almost certainly a bug waiting to happen.
Here’s where we deploy never as a safety net.
function getArea(shape: Shape): number {
switch (shape.kind) {
case "circle":
return Math.PI * shape.radius ** 2;
case "square":
return shape.sideLength ** 2;
case "triangle":
return (shape.base * shape.height) / 2;
default:
// We assign the remaining case to a variable of type 'never'
const _exhaustiveCheck: never = shape;
return _exhaustiveCheck;
}
}
Now, watch the magic. If someone adds "hexagon" to the Shape union, the default case suddenly tries to assign a { kind: "hexagon" } object to a variable that only accepts never. This is impossible, so TypeScript will throw a beautiful, compile-time error:
Type '{ kind: "hexagon"; }' is not assignable to type 'never'.
You’ve just turned a runtime logic error into a compile-time type error. This is a huge win. You’re notified the moment the union changes, forcing you to handle the new case. It’s the type system working for you, not against you.
A Note on the Pitfall: Empty Arrays
Here’s a weird one that often trips people up. TypeScript will infer a never type for an empty array if you don’t provide a type annotation. It’s its way of saying, “I have no idea what you intend to put in here, so I’ll assume nothing can go in.”
const myArray = []; // type is never[]
myArray.push("something"); // Error: Argument of type 'string' is not assignable to parameter of type 'never'.
The designers were being “safe,” but it’s mostly just annoying. The fix is simple: just tell TypeScript what you mean.
const myArray: string[] = []; // or initialize with a value: ['something']
myArray.push("works fine now");
So, embrace never. Don’t think of it as the type of nothing; think of it as the type of impossibility. It’s a tool for making impossible states impossible, and that’s one of the most powerful concepts in type-safe programming.