Command Palette

Search for a command to run...

Hectal
PHASE 5Beginner ~27 min· topic 5 of 6

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. never is TypeScript's bottom type.
Top type
A type every value fits into. unknown is 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".

fail.tswhole filets
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.

port.tswhole filets
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.

sets.tswhole filets
type A = string | never;            // string
type B = string & number;           // never
type C = { kind: "a" } & { kind: "b" }; // never

04The 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.

light.tswhole filets
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
  }
}
The exhaustiveness signaldiagram
Rendering diagram…

Try it yourself

  1. 1

    Add a case to the union

    In the third example, change type Light to "red" | "green" | "blue". Predict where the error appears and what left shows: left is now "blue", and unreachable(left) fails because "blue" isn't never.

  2. 2

    Remove the never annotation

    In the second example, delete : never from fail. Predict which function now has a problem: onlyString, which no longer ends with a return the compiler can see (TS2366).

  3. 3

    Make a never by accident

    Add type D = "x" & "y"; and read it with // ^?. Predict never. Then try type E = ("x" | "y") & "x"; and guess before you look (it is "x": the part that doesn't overlap is removed).

Code & diagrams

Never in unions and intersections

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

1
A function that never returns

onlyString 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: abc
never as the exhaustiveness signal, and the bottom of the type lattice

In 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
function

Break 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.

terminal
$ Types panel
── what you'll see ──
index.ts(11,42): error TS2366: Function lacks ending return statement and return type does not include 'undefined'.

Break #2

Push into an empty typed array

Declare const empty: never[] = []; and call empty.push(1);.

terminal
$ Types panel
── what you'll see ──
index.ts(8,12): error TS2345: Argument of type '1' is not assignable to parameter of type 'never'.

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.

  • ▸

    never is the identity of union and the absorbing element of intersection: T | never is T, and T & never is never. Distributive conditional types (Topic 9.2) apply this: when a naked type parameter is instantiated with never, a distributive conditional over it produces never rather 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" } is never while { name: string } & { age: number } is fine. It can hide mistakes: if two unions are combined with & and the result is unexpectedly never, 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 never return 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 () => never is assignable to () => void and to () => string, but not the other way around, because the return type is covariant: a subtype may be returned where a supertype is expected, and never is a subtype of everything (Topic 7.7 covers variance).

Remember this

  1. 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: number is the set of all numbers, "a" | "b" has two values. **never is the empty set**: no value belongs to it. That is why you can never produce one, why const x: never = 5 is an error, and why never is 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. 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 : never explicitly on function declarations; if you leave the annotation out, TypeScript infers void for a declaration whose body only throws. The annotation also has an effect on the code around the call: after fail(...) the compiler knows the line below is unreachable, so a function can end with a call to fail and not need a return.

  3. 3

    **never disappears from unions.** string | never is just string, 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 as Exclude<T, U> (Topic 10.2) work by turning the unwanted members into never, and mapped types (Topic 8.5) drop keys the same way.

  4. 4

    **Impossible intersections become never.** string & number can't be both, so it is never. Two object types whose property has contradictory literal types collapse the same way: { kind: "a" } & { kind: "b" } is never, because no object can have kind equal 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. 5

    **never is assignable to everything (the empty set is inside every set), but nothing except never is assignable to never**. So const s: string = nothing is fine when nothing: never: the code path can't run, so it can't break the promise. This is also why a function returning never can be used where any return type is wanted, such as () => string.

  6. 6

    **never as a signal.** After a switch or an if chain has handled every member of a union, the variable that is left has type never. If you pass that variable to a function that takes a never parameter, the code compiles only when every case really was handled. When someone later adds a new member to the union, the leftover is no longer never, and the compiler points at every place that forgot the new case. Topic 4.6 builds this idea into the assertNever helper; this topic shows why it works.

  7. 7

    Common accidental nevers: an empty array literal given the type never[] (a real run shows list: never[] for { list: [] } and never[] 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 as never and you did not expect it, hover the pieces with // ^? and look for the step where the set emptied.

Explain it without notes

01

What is the never type, and how is it different from void?

02

Why is string | never the same as string, and string & number the same as never?

03

How does never help you write exhaustive switch statements?

04

Name three ways never appears without you writing it.

Practice

01

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.

02

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.

03

Predict, then check: type X = ("a" | "b") & ("b" | "c"), and type Y = string & "a".

Trade-offs

  • ↔

    Exhaustiveness checks via never make adding a union member safe, at the cost of one helper function and a default branch. In very large switches, it is still the cheapest protection against forgotten cases.

  • ↔

    Because never silently collapses types, a mistake in a type definition can turn a whole value into never, and every use then reports a confusing error. Hover the intermediate types when a result is never.

Done when you can

  • I can say that never is the empty type and why nothing can be assigned to it.

  • I can write a function that returns never and explain what it changes about the code after the call.

  • I can predict string | never, string & number and { kind: "a" } & { kind: "b" }.

  • I can explain how a default branch typed never finds forgotten cases.

  • I can tell never from void and unknown.