any, unknown and never
Three special types sit at the edges of TypeScript's system: any turns type checking off,
unknown is its safe replacement, and never is the type of a value that cannot exist.
Understanding all three — and specifically preferring unknown over any — is one of the clearest
dividing lines between beginner and competent TypeScript.
any — the off switch
any means "stop checking this — I take responsibility." A value typed any can do anything, be
assigned anywhere, and the compiler says nothing:
let x: any = "Pune";
x.toFixed(); // no error — even though a string has no toFixed
x = 42; // no error
x = { foo: true }; // no error
x.foo.bar.baz; // no error — TypeScript has given up on x entirely
const n: number = x; // no error — any flows into anything
Every line there is a potential runtime crash, and TypeScript flags none of them. any is a hole
in the type system: once a value is any, all the guarantees you came to TypeScript for are gone
for that value — and worse, any is contagious. It spreads: const n: number = x makes n a
number that might actually hold anything, and the unsafety leaks onward.
This is why a codebase full of any gets almost none of TypeScript's benefit — it is JavaScript with
extra syntax. The instinct to sprinkle any to make an error message disappear is the single most
common way beginners neuter the tool.
unknown — the safe alternative
unknown is any's responsible cousin. It also holds any value — but unlike any, it will not let
you do anything with that value until you have proven what it is:
let x: unknown = "Pune";
x.toFixed(); // error: 'x' is of type 'unknown'
const n: number = x; // error: Type 'unknown' is not assignable to type 'number'
if (typeof x === "string") {
x.toUpperCase(); // fine! inside this block, TypeScript knows x is a string
}
unknown says "this could be anything, so you must check before you use it." You cannot call a
method, access a property, or assign it to a specific type until you have narrowed it (with typeof,
a check, a type guard — the narrowing module). This is exactly the safety you want for values whose
type you genuinely do not know at compile time — and it is why unknown is almost always the right
choice where you were tempted to write any.
The classic use is a value from outside your program:
const data: unknown = JSON.parse(input); // JSON.parse could return anything
// data.name; // error — you must check the shape first
if (typeof data === "object" && data !== null && "name" in data) {
// now you can safely work with data.name
}
JSON.parse returns any by default (a historical wart), but the moment you assign it to unknown,
TypeScript forces you to validate before trusting it. Prefer unknown for external data, and let
the compiler make you check.
any versus unknown — the rule
Both accept any value. The difference is what happens next:
any— you can do anything with it, no checks, no safety. It turns checking off.unknown— you can do nothing with it until you prove its type. It keeps checking on.
The rule: never reach for any when unknown will do — which is almost always. any says "I
give up on types here"; unknown says "I do not know the type yet, so make me check." The second is
safe; the first is a hole. When you are genuinely tempted by any, ask whether unknown plus a check
would work — it usually does, and it keeps the guarantee.
never — the impossible type
never is the type of a value that can never occur. It sounds abstract, and it has two very
practical uses.
A function that never returns — because it always throws, or loops forever — returns never:
function fail(message: string): never {
throw new Error(message); // never returns normally
}
The type says "this function does not produce a value; it ends the program flow." That is more honest
than void (which means "returns nothing useful") — never means "does not return at all".
Exhaustiveness checking — the genuinely important use, which the unions module builds on fully.
When you handle every case of a union, the "impossible remaining case" has type never, and you can
make TypeScript prove you handled everything:
type Status = "pending" | "paid" | "shipped";
function label(status: Status): string {
switch (status) {
case "pending": return "Pending";
case "paid": return "Paid";
case "shipped": return "Shipped";
default:
const exhaustive: never = status; // if a case is missing, status is not `never` -> compile error
return exhaustive;
}
}
If you later add "delivered" to Status and forget to handle it, that const exhaustive: never = status line fails to compile, because status could now be "delivered", which is not never.
The compiler walks you to the missing case. This is one of TypeScript's best safety features, and
never is what makes it work — you will see it properly in the exhaustiveness lesson.
Check your work
What any does. Turns off type checking for a value — it can do anything, assign anywhere, with
no errors.
Why any is dangerous, and contagious. Every unsafe operation on it is unchecked, and it spreads
its unsafety to whatever it is assigned to.
What unknown does. Holds any value, but permits nothing until you prove its type by narrowing.
The rule for any versus unknown. Prefer unknown — it keeps checking on; reach for any only
in rare, deliberate cases.
The classic use of unknown. External data (JSON.parse, API responses), which you must validate
before trusting.
What never is. The type of a value that can never occur.
never versus void. never means the function does not return at all (throws/loops); void
means it returns nothing useful.
never's important use. Exhaustiveness checking — assigning the "impossible" case to never makes
the compiler prove every union case is handled.
Practice
- Type a value
anyand do five unsafe things to it (call a wrong method, access nested nonsense, assign it to anumber). Confirm TypeScript complains about none of them. - Change it to
unknownand try the same five things. Read each error. - Narrow the
unknownwithtypeof x === "string"and confirm you can use it inside the block. - Assign
JSON.parse(...)tounknownand confirm you must check its shape before accessing a property. - Write a
fail(message): neverthat throws, and confirm the return type isnever. - Build the
labelfunction with theneverexhaustiveness check. Add a new status to the union and watch thedefaultbranch fail to compile. Handle it to fix it. - Explain, in one sentence each, when you would use
any,unknown, andnever.
Official documentation
- TypeScript — The
anytype — And why to avoid it. - TypeScript —
unknown— The safe top type. - TypeScript —
never— Functions that never return, and exhaustiveness.
Next: inference — when to annotate, and when to let TypeScript do it.
Stuck on this lesson?
Being stuck is part of it — but being stuck alone for three days is not. Our internship programme pairs this curriculum with code review and one-to-one help from working developers, and it is free.
About the internship