codejump

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

Waiting at the end of this page.

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.

TypeMeansYou can
anystop checkingdo anything, including the wrong thing
unknownsomething, but prove whatnothing, until you narrow it
neverno value existsnothing — 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.

TypeScript
const data: any = JSON.parse(raw);
data.user.profile.name.toUpperCase();  // compiles
                                       // TypeError at runtime, three levels down

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

TypeScript
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:

TypeScript
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:

TypeScript
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 quiz

Reading this page is the cheap half. These are the exercises that make you use it.