Two return types for "nothing"
You met void in lesson 3-2: the function finishes normally but returns nothing worth using. console.log returns void.
never is stronger. The function never finishes normally at all. It always throws, or loops forever. No value ever comes back, not even undefined.
function fail(message: string): never { throw new Error(message); }
The distinction earns its keep because the compiler understands it. After a call to a never function, code is unreachable, so TypeScript stops demanding a return value on that path. And in a discriminated union switch (lesson 4-3), assigning the leftover case to never proves you handled every member. Forget one and the compiler tells you which.
A never function inside a function that returns a string
fail is typed never because it always throws, and seatLabel can call it in a branch while still promising a string.
function fail(message: string): never { throw new Error(message); } function seatLabel(row: number): string { if (row < 1) { return fail("row must be positive"); } return "Row " + row; } console.log(seatLabel(4)); console.log(seatLabel(12));
Output
Row 4 Row 12
seatLabel returns a string on every normal path, and the compiler accepts the fail branch because nothing survives it. There is no execution in which that return produces a non-string, since it produces nothing at all.
Change fail's return type to void and the return fail(...) line stops type-checking. The distinction is exact: void is a real value that a caller could receive and would not be a string, while never describes an outcome that cannot happen.
Exhaustiveness checking with never
This is the pattern from the previous section running live.
type Command = | { kind: "add"; amount: number } | { kind: "reset" }; function apply(total: number, cmd: Command): number { switch (cmd.kind) { case "add": return total + cmd.amount; case "reset": return 0; default: const impossible: never = cmd; return impossible; } } let total = 0; total = apply(total, { kind: "add", amount: 5 }); total = apply(total, { kind: "add", amount: 3 }); console.log("after adds: " + total); total = apply(total, { kind: "reset" }); console.log("after reset: " + total);
Output
after adds: 8 after reset: 0
Why the odd-looking default branch exists
- By the time control reaches
default, every union member has been handled, socmdhas narrowed tonever. Nothing is left, and assigning it to anevervariable compiles. - The branch is unreachable today. The
neverassignment exists purely so the compiler complains the day the union grows. - Add a third member
{ kind: "double" }toCommandwithout adding a case, and that line becomes a compile error naming the forgotten member. The type system turns a silent fall-through into a build failure. - This is the standard way production codebases keep switches in sync with their unions.
The right return type for a logging function
A logEvent function whose body is a single console.log call followed by a normal end should be typed void.
Finishing normally without producing a meaningful value is exactly what void describes. The function runs, has its effect, and control returns to the caller with nothing worth reading.
never is reserved for functions that cannot finish at all, such as ones that always throw or loop forever. Annotating logEvent as never would be a compile error, because the function does return, and never promises that it does not.
Overloads, the lightweight way
Sometimes one function accepts several input forms. Full TypeScript overloads list multiple signatures above one implementation:
function len(x: string): number; function len(x: string[]): number; function len(x: string | string[]): number { return x.length; }
Callers see the two precise signatures, the implementation handles the union. In practice, most code does not need this ceremony: a plain union parameter plus narrowing (Unit 4) covers the majority of cases, and unions should be your first reach. Recognize overload syntax when you read library code, write it only when a union genuinely cannot express the input-output relationship.
Typing a function that only throws
A function whose entire body is throw new Error("nope") has the most precise return type never.
The reasoning is direct. A function that always throws cannot return a value, not even undefined, so there is no type that describes what comes back. never is the type with no values at all, which is precisely the right answer.
void would be wrong because it claims the function finishes normally and simply has nothing useful to hand over. This function does not finish normally, so void overstates what happens. never is the type introduced at the top of this lesson, and this is its clearest use.