AcademyTypeScript
Where any, unknown and never belong
The three types that are not about your data: one switches the compiler off, one makes you prove things, and one means this cannot happen.
3 min readUpdated 3 exercises
Three types about the type system itself
Most types describe your data. These three describe what the compiler is allowed to assume, and every codebase uses at least one of them wrongly.
| Type | Means | You can |
|---|---|---|
any | stop checking | do anything, including the wrong thing |
unknown | something, but prove what | nothing, until you narrow it |
never | no value exists | nothing — reaching it is the bug |
any is not a type, it is an off switch
any does not describe a value. It tells the compiler to stop asking, and the silence spreads: every property you read off an any is itself any.
const data: any = JSON.parse(raw);
data.user.profile.name.toUpperCase(); // compiles
// TypeError at runtime, three levels downThat line is the reason noImplicitAny exists. Nothing there is checked — not the property, not the chain, not the method. A single any at the top of a function can un-type everything downstream of it, and no error ever points back at where it started.
unknown is the honest version
unknown says the same thing — "I do not know what this is" — without giving up. It accepts any value, and lets you do nothing with it until you narrow.
const data: unknown = JSON.parse(raw);
data.user; // Error: 'data' is of type 'unknown'
if (typeof data === 'object' && data !== null && 'user' in data) {
data.user; // fine — you proved it
}This is the right type for every value crossing a boundary you do not control: JSON.parse, a fetch body, a webhook payload, a catch clause. It costs you one narrowing at the edge and buys real checking everywhere after it.
catch is the case people meet first. Since TypeScript 4.4 the clause variable is unknown, because anything can be thrown — a string, a number, undefined:
try {
await save();
} catch (error) {
// error.message — Error: 'error' is of type 'unknown'
const message = error instanceof Error ? error.message : String(error);
}never is a promise the compiler holds you to
never is the type with no values. A function returning never does not return at all — it throws or loops forever. More usefully, it is what a union has left after you have handled every member, which turns into a compile-time exhaustiveness check:
type Shape =
| { kind: 'circle'; r: number }
| { kind: 'square'; side: number };
function area(shape: Shape): number {
switch (shape.kind) {
case 'circle': return Math.PI * shape.r ** 2;
case 'square': return shape.side ** 2;
default: {
const exhaustive: never = shape; // shape is never here — nothing is left
throw new Error('Unhandled shape: ' + JSON.stringify(exhaustive));
}
}
}Add a third member to Shape and that assignment stops compiling, in every switch that forgot it. This is the single highest-value trick in the language: it turns "find every place that handles this union" from a search into a build error.
The rule
Use unknown where you would reach for any, and never to make the compiler prove you covered a union. any earns its place in exactly two situations — a migration in progress, and a type-level escape hatch you have commented — and both should be rare enough to grep for.
Now practice it
2 challenges · 1 quizReading this page is the cheap half. These are the exercises that make you use it.
- Exhaustive Discriminated Union (never)ProThe never assignment in a default branch, written until adding a union member breaks the build on purpose.ChallengeSolve · MediumChallengeSolve · Medium
- Assertion Functions (asserts val is T)ProAn assertion function is how you turn unknown into something usable without reaching for any.ChallengeSolve · EasyChallengeSolve · Easy
- Fundamentals · TypeScript FundamentalsFundamentals questions covering what each of these three types allows, and what it silently permits.QuizTake · EasyQuizTake · Easy
Read next
Narrowing: how a union becomes one type
The checks TypeScript understands — typeof, instanceof, in, truthiness, discriminants — and what makes a narrowing silently fall apart again.
3 min
Literal types, unions and as const
Why a string in a variable stops being a literal, what as const freezes, and why a union of strings beats an enum almost every time.
3 min