When Something Should Never Happen: Using never Correctly in TypeScript
Using never correctly is less about handling errors and more about defining impossible states, allowing TypeScript to enforce that certain paths or combinations should never exist.
At first, never looks like a strange type.
You see it in examples like:
function fail(): never {
throw new Error("fail")
}And it feels⌠limited.
Almost like a niche case for functions that throw errors.
But thatâs not where never becomes useful.
The real value of never appears when you stop thinking about it as a return type, and start seeing it as a way to describe something deeper:
A situation that should be impossible
The Shift: From Values to Possibilities
By now, youâve seen this idea:
Type = set of possible valuesSo if a type represents all possible values something can have, then never represents:
No possible values at allWhich means:
There is nothing left. Nothing valid. Nothing allowed.
And thatâs exactly what makes it powerful.
The Most Important Use: Exhaustiveness
This is where never becomes practical.
Imagine you have:
type Status = "success" | "error"And you handle it:
function handle(status: Status) {
switch (status) {
case "success":
return "ok"
case "error":
return "fail"
}
}This looks fine.
Until one day, someone adds:
type Status = "success" | "error" | "pending"Now your function silently ignores "pending".
No error. No warning. Just a hidden bug.
This is where never comes in.
function handle(status: Status) {
switch (status) {
case "success":
return "ok"
case "error":
return "fail"
default:
const _exhaustive: never = status
return _exhaustive
}
}Now something important happens.
If a new case is added and not handled, TypeScript complains:
Type '"pending"' is not assignable to type 'never'That error is not about types.
Itâs about logic.
Itâs TypeScript telling you:
âYou thought this was impossible. But itâs not anymore.â
Making Impossible States Explicit
You can also use never to prevent invalid combinations.
type Props =
| { type: "text"; value: string; numberValue?: never }
| { type: "number"; numberValue: number; value?: never }Now this is invalid:
{
type: "text",
value: "hello",
numberValue: 123
}Because:
numberValue must never exist in this caseHere, never acts as a guardrail.
Not for values you expect.
But for values you want to forbid.
Where People Use It Incorrectly
A common mistake is thinking:
âIf my function throws an error, I should include never in the return typeâLike this:
function getUser(): User | neverBut this doesnât make sense.
Because:
never adds no possible valuesSo:
User | never â UserMore importantly, throwing an error is not a return value.
Itâs a break in execution.
So never is not about error handling.
Itâs about impossibility.
The Real Pattern
Once you see it clearly, all correct uses of never follow the same idea:
âThis state should not existâIn different forms:
- No remaining cases â
never - Invalid property â
never - Impossible branch â
never
Itâs always about removing possibilities, not adding them.
A Small but Powerful Tool
You wonât use never often.
And when you do, itâs usually in small places.
A default case.
A type definition.
A helper function.
But those small places have an outsized impact.
Because they turn silent assumptions into explicit guarantees.
The Bigger Insight
never is not about writing more code.
Itâs about making your assumptions visible.
It forces you to answer:
âWhat should be impossible in this system?â
And once you define that clearly, TypeScript can enforce it for you.
Not by guessing.
But by eliminating every path that should never exist.