Any, Unknown, Never
The escape hatch, the safe top type, and the empty bottom type
TypeScript's type system has three "special" types that sit at the edges
of the type lattice: any, unknown, and never. They're
often misunderstood, but they're essential for expressing concepts like
"opt-out of type checking" (any), "accept anything but force narrowing"
(unknown), and "this code is unreachable" (never). A fourth type,
void, is also worth understanding in relation to these three.
This chapter answers: What is any and when should you (not) use it?
How is unknown different? What does never represent? How do
these types relate to each other?
The type hierarchy: top and bottom
Imagine the type system as a lattice. At the very top sits a type that accepts every value — the top type. At the very bottom sits a type that accepts no values — the bottom type. Every other type falls somewhere in between.
unknownis the true top type: every value is assignable tounknown.neveris the bottom type: no value is assignable tonever, andneveris assignable to every type.anyis the escape hatch: it bypasses the type system entirely.
any: the escape hatch
The type any disables type checking. A value of type any can be
assigned to anything, and anything can be assigned to any. You can call
any method, access any property, and the compiler won't complain:
When is any acceptable?
- During migration from JavaScript to TypeScript, when you don't yet have time to type everything.
- When working with truly dynamic data (e.g., JSON from an external API you don't control, where the shape is unknown).
- When calling legacy JavaScript libraries without type definitions.
When is any dangerous?
- Everywhere else. It's a trapdoor: once a value becomes
any, the compiler loses all knowledge of its type. Bugs slip through.
The function double compiles without errors, but it produces nonsense
at runtime when passed non-numbers. This is exactly what TypeScript is
supposed to prevent. Avoid any whenever possible.
unknown: the safe top type
The type unknown is the type-safe alternative to any. Like any,
it accepts every value — but unlike any, you can't do anything with
an unknown value until you narrow it:
To use an unknown value, you must prove its type via narrowing:
What bugs does this prevent? It forces you to handle the "I don't
know what this is" case explicitly. You can't accidentally call a method
that doesn't exist. This is far safer than any.
When to use unknown:
- When you truly don't know the type of a value (e.g., parsing JSON, reading from an external API).
- As the parameter type for generic utilities that accept any value but require narrowing before use.
Compare any vs unknown side by side:
The safeUnknown function is longer, but it's correct — it handles all
cases without crashing.
never: the empty type
The type never represents the empty set — a type with no values. No
value is assignable to never (except never itself). Where does this
come up?
Functions that never return
These functions throw or loop forever, so they don't have a return value at all:
Impossible branches in control flow
In the default branch, shape is type never — the compiler has
eliminated all possible cases, so no value can reach here. If you add a
new variant (say, { kind: "triangle" }), the default line will
error, forcing you to handle it.
Intersection of disjoint types
Why is never useful? It lets you express "this code is
unreachable" or "this type is impossible" in a way the compiler
understands. When you see never, it's a signal that the compiler has
proven something can't happen.
void: the "no useful return value" type
The type void means "this function is called for its side effects, not
its return value." It's not quite the same as undefined (though
functions that return void typically return undefined at runtime):
void is a signal to readers (and the compiler) that the function's
return value doesn't matter. You can assign the result to a variable,
but the compiler won't let you use it in a meaningful way:
void is the conventional return type for event handlers, loggers, and
other side-effect functions.
void vs undefined
There's a subtle distinction:
voidmeans "I don't care about the return value."undefinedmeans "the return value is explicitlyundefined."
In practice, void is more common. Use undefined when the return type
matters (e.g., a function that might return T | undefined to signal
absence).
Type relationships: assignability
Here's how the four types relate in terms of assignability:
anybypasses the type system: you can assignanyto anything, and anything toany.unknownis the top type: you can assign anything tounknown, but you can't assignunknownto anything (without narrowing).neveris the bottom type: you can't assign anything tonever, but you can assignneverto anything (this is useful for exhaustiveness checking).voidis a return-type-only marker: you can assignundefinedtovoid, but not vice versa.
Practice: a safe JSON parser
Let's solidify these concepts with a challenge. You'll write a function
that parses JSON and uses unknown to ensure type safety.
Implement parseJSON(text: string): unknown that wraps JSON.parse and returns unknown (not any). Then implement getString(value: unknown): string that returns the value if it's a string, or throws an error otherwise. Test with valid and invalid inputs.
When to use each type
Use any when:
- You're migrating JavaScript to TypeScript and need a temporary escape hatch.
- You're interacting with truly dynamic code (e.g., a library without types).
- You understand the risks and have a plan to narrow or refine later.
Use unknown when:
- You don't know the type at compile time (e.g., parsing JSON, reading user input).
- You want to force yourself (or consumers) to narrow before use.
- You're writing generic utilities that accept any value.
Use never when:
- A function never returns (throws or loops forever).
- You want exhaustiveness checking in a
switchorif/elsechain. - You're expressing "this type is uninhabited" (e.g., intersection of disjoint types).
Use void when:
- A function is called for side effects, not its return value.
- You're defining callback signatures (e.g., event handlers).
Check your understanding
What is the key difference between any and unknown?
unknown is deprecated, use any
any is type-safe, unknown is not
any bypasses type checking, unknown forces narrowing
They are identical
What does the never type represent?
A function that returns nothing (void)
A value that is null or undefined
A type with no values (the empty set)
A value that is both string and number
Summary
any is the escape hatch — it disables type checking entirely. Use it
sparingly, and only when you have no better option. unknown is the
type-safe top type — it accepts any value but forces you to narrow before
use. never is the bottom type — it represents impossible values,
unreachable code, and exhaustiveness checks. void is a return-type
marker for side-effect functions.
Together, these four types let you express the full range of type
constraints: "accept anything" (unknown), "accept nothing" (never),
"don't care about the return" (void), and "opt-out of type checking"
(any). Mastering them is essential for writing precise, safe TypeScript
code.
This concludes the Core Type System chapter. You've now seen
primitives, arrays, tuples, objects, interfaces, type aliases, literals,
functions, unions, intersections, narrowing, and the special types
any/unknown/never/void. In the next chapters, we'll build on this
foundation to explore generics, advanced type patterns, and type-driven
architecture.