Topic 5.5
never in Depth
In one line
never is the type with no values at all. It is what a function that never returns gives back, what a check leaves when every possibility has been ruled out, and the way the compiler tells you "this should be impossible".
Think of it like this
A lift button for floor 13 in a building that has no floor 13. If someone claims to have pressed it, something is wrong: no real person can have done that. **never is the type of "this can't happen"**. It is used for things that cannot exist: a value that can't be produced, or a line of code that can't be reached.
Words you'll meet
New words in this topic, in plain English. Come back here whenever one feels fuzzy.
- never
- The type with no values. Nothing can be of this type.
- Bottom type
- A type that is inside every other type.
neveris TypeScript's bottom type. - Top type
- A type every value fits into.
unknownis TypeScript's top type. - Unreachable code
- Code that can never run, for example the line after a
throw. - Intersection
- A type written
A & B: a value that is both A and B at once. - Exhaustive
- Covering every possible case, with none forgotten.
- Control flow
- The path the program may take through your code. TypeScript follows it to narrow types.
- void
- The return type of a function that returns nothing useful. Different from
never: a void function does return, just without a value.
Step by step
01Never means no value at all
void says "this function finishes, but there is nothing to use". never says "this function does not finish". A function that throws on every call or loops forever has never because there is no "after".
function fail(message: string): never {
throw new Error(message);
}02It helps the compiler know what is reachable
parsePort must return a number. If Number(text) is NaN, it calls fail, which never comes back. The checker knows that code after fail(...) in that branch is dead, so n is a real number at return n.
The same knowledge lets a function whose last statement is fail(...) skip the final return.
function parsePort(text: string): number {
const n = Number(text);
if (Number.isNaN(n)) {
fail("not a number: " + text);
}
return n;
}03Unions and intersections
In a union, never vanishes. In an intersection with nothing in common, everything vanishes into never.
type A = string | never; // string
type B = string & number; // never
type C = { kind: "a" } & { kind: "b" }; // never04The exhaustiveness signal
After the two cases below, l has been narrowed away completely. Its type in default is never. Sending it to a never parameter makes the compiler check your work; add "blue" to Light and the default branch stops compiling until you handle it.
type Light = "red" | "green";
function next(l: Light): Light {
switch (l) {
case "red": return "green";
case "green": return "red";
default: return unreachable(l); // l is never
}
}Try it yourself
- 1
Add a case to the union
In the third example, change
type Lightto"red" | "green" | "blue". Predict where the error appears and whatleftshows:leftis now"blue", andunreachable(left)fails because"blue"isn'tnever. - 2
Remove the never annotation
In the second example, delete
: neverfromfail. Predict which function now has a problem:onlyString, which no longer ends with a return the compiler can see (TS2366). - 3
Make a never by accident
Add
type D = "x" & "y";and read it with// ^?. Predictnever. Then trytype E = ("x" | "y") & "x";and guess before you look (it is"x": the part that doesn't overlap is removed).
Code & diagrams
A is plain string (never disappears), B and C are never. The empty array typed never[] can never receive an item, and no value can be given the type B. The code still runs: push really works at runtime, which is why the console prints 1.
Console
(nothing was printed)Expected console output
1onlyString has no return after fail(...) and still compiles, because the compiler knows fail never comes back. Remove : never from fail and the TS2366 error appears (it is in the Break it section). The last line shows that no value is ever a never.
Console
(nothing was printed)Expected console output
8080 ok
not a number: abcIn default, left is never because both cases were handled. Try adding | "blue" to Light: the line unreachable(left) becomes an error. In bottom, a never value is accepted as a string, a number and a function result, because it can never actually exist.
Console
(nothing was printed)Expected console output
green
functionBreak it on purpose
Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.
Break #1
Leave out the never annotation
In the second example, delete : never from the first line (function fail(message: string) {). Only onlyString gets an error.
Break #2
Push into an empty typed array
Declare const empty: never[] = []; and call empty.push(1);.
Myth vs fact
Myth
never is the same as void.
Fact
void means a function returns without a useful value. never means it doesn't return at all, or that a value cannot exist. A void function can be called and finishes; a never function cannot finish.
Myth
never is a type you rarely meet.
Fact
You meet it daily without knowing: exhaustiveness checks, filtered utility types, impossible intersections, empty array literals, and catch-all branches all produce never.
Myth
any and never are opposites in the same way.
Fact
unknown is the opposite of never (everything vs nothing). any is a special escape hatch that is both assignable to and from nearly every type, and sits outside the normal ordering.
Pro corner
Extra depth for experienced readers. New to this? Skip it for now and come back later.
- ▸
neveris the identity of union and the absorbing element of intersection:T | neverisT, andT & neverisnever. Distributive conditional types (Topic 9.2) apply this: when a naked type parameter is instantiated withnever, a distributive conditional over it producesneverrather than evaluating a branch, because it distributes over an empty union. - ▸
The compiler reduces intersections of discriminant properties with disjoint literal types to
never(since TypeScript 3.9). That is why{ kind: "a" } & { kind: "b" }isneverwhile{ name: string } & { age: number }is fine. It can hide mistakes: if two unions are combined with&and the result is unexpectedlynever, look for a shared tag with different literals. - ▸
Control-flow analysis treats a call as unreachable-after only when the called function has an **explicit
neverreturn annotation** and the call target is named by an identifier or a property access whose declaration also has an explicit type. A function expression assigned to a variable without an annotation does not count. This is the same rule as for assertion functions (Topic 4.7). - ▸
A function type
() => neveris assignable to() => voidand to() => string, but not the other way around, because the return type is covariant: a subtype may be returned where a supertype is expected, andneveris a subtype of everything (Topic 7.7 covers variance).
Remember this
- 1
The problem this solves: Some code paths and results are impossible by design, but without a type for that, the checker can't tell you when you've reached one. Types are sets of values:
numberis the set of all numbers,"a" | "b"has two values. **neveris the empty set**: no value belongs to it. That is why you can never produce one, whyconst x: never = 5is an error, and whyneveris called the bottom type: it sits under every other type, like the empty set inside every other set. (unknown, Topic 5.4, is the opposite end: the set of everything.) - 2
Functions that never return have the return type
never. A function that always throws, or loops forever, never hands a value back to its caller:function fail(message: string): never { throw new Error(message); }. Be careful to say: neverexplicitly on function declarations; if you leave the annotation out, TypeScript infersvoidfor a declaration whose body only throws. The annotation also has an effect on the code around the call: afterfail(...)the compiler knows the line below is unreachable, so a function can end with a call tofailand not need areturn. - 3
**
neverdisappears from unions.**string | neveris juststring, in the same way that adding nothing to a set changes nothing. This is not a curiosity: it is how the type system *removes* members. Utility types such asExclude<T, U>(Topic 10.2) work by turning the unwanted members intonever, and mapped types (Topic 8.5) drop keys the same way. - 4
**Impossible intersections become
never.**string & numbercan't be both, so it isnever. Two object types whose property has contradictory literal types collapse the same way:{ kind: "a" } & { kind: "b" }isnever, because no object can havekindequal to both. You'll sometimes meet this by accident, when you combine types (Topic 3.3), and the error shows up where you use the value:Type '"text"' is not assignable to type 'never'. - 5
**
neveris assignable to everything (the empty set is inside every set), but nothing exceptneveris assignable tonever**. Soconst s: string = nothingis fine whennothing: never: the code path can't run, so it can't break the promise. This is also why a function returningnevercan be used where any return type is wanted, such as() => string. - 6
**
neveras a signal.** After aswitchor anifchain has handled every member of a union, the variable that is left has typenever. If you pass that variable to a function that takes aneverparameter, the code compiles only when every case really was handled. When someone later adds a new member to the union, the leftover is no longernever, and the compiler points at every place that forgot the new case. Topic 4.6 builds this idea into theassertNeverhelper; this topic shows why it works. - 7
Common accidental
nevers: an empty array literal given the typenever[](a real run showslist: never[]for{ list: [] }andnever[]as the return type of a function that returns[]; you can't push anything into it), a union narrowed past its last member, an intersection of unrelated primitives, and a conditional type with no matching branch (Phase 9). When a type shows up asneverand you did not expect it, hover the pieces with// ^?and look for the step where the set emptied.
Explain it without notes
What is the never type, and how is it different from void?
Why is string | never the same as string, and string & number the same as never?
How does never help you write exhaustive switch statements?
Name three ways never appears without you writing it.
Practice
Write assertNever(x: never): never and use it in the default of a function over type Shape = { kind: "circle"; r: number } | { kind: "square"; s: number }. Then add a "triangle" member and note the error.
Write function must<T>(value: T | undefined, message: string): T that returns the value or calls a never function. (Generics are Phase 7: copy the signature.) Explain why no return undefined is needed.
Predict, then check: type X = ("a" | "b") & ("b" | "c"), and type Y = string & "a".
Trade-offs
- ↔
Exhaustiveness checks via
nevermake adding a union member safe, at the cost of one helper function and adefaultbranch. In very large switches, it is still the cheapest protection against forgotten cases. - ↔
Because
neversilently collapses types, a mistake in a type definition can turn a whole value intonever, and every use then reports a confusing error. Hover the intermediate types when a result isnever.
Done when you can
I can say that
neveris the empty type and why nothing can be assigned to it.I can write a function that returns
neverand explain what it changes about the code after the call.I can predict
string | never,string & numberand{ kind: "a" } & { kind: "b" }.I can explain how a
defaultbranch typedneverfinds forgotten cases.I can tell
neverfromvoidandunknown.