unknown vs any
unknown and any are both “top types” — every value is assignable to them — but they behave like opposites once you try to use the value. any switches the type checker off: you can call anything, access any property, and assign it anywhere, with no warnings and no safety. unknown keeps the checker on: you may hold a value of unknown type, but you must prove what it is before doing anything with it. The rule of thumb is simple — when you do not know a value’s type, reach for unknown, and treat any as a last resort you actively avoid. This page contrasts the two and shows how to handle unknown safely.
any disables the checker
Assigning a value to any opts every downstream operation out of type checking. The compiler stops protecting you, and mistakes surface only at runtime.
const data: any = JSON.parse('{"name":"Ada"}');
data.toUpperCase(); // no error — crashes at runtime (it's an object)
data.foo.bar.baz; // no error — crashes at runtime
const n: number = data; // no error — silently wrong
None of these lines is flagged. Worse, any is contagious: it spreads through every expression it touches, quietly eroding the safety of code far from the original cast.
unknown keeps the checker on
unknown accepts any value too, but forbids operating on it until you narrow it to something specific. The same mistakes become compile errors.
const data: unknown = JSON.parse('{"name":"Ada"}');
// ❌ Error: 'data' is of type 'unknown'.
data.toUpperCase();
if (typeof data === "object" && data !== null && "name" in data) {
// narrowed: data is now object with a 'name' key
console.log((data as { name: string }).name);
}
unknown says “I have a value but no guarantees about it,” which is exactly the honest type for parsed JSON, catch variables, or third-party data. You unlock it only after a type guard proves its shape.
The key differences
| Aspect | any | unknown |
|---|---|---|
| Accepts any value | Yes | Yes |
| Use without narrowing | Yes (unchecked) | No (compile error) |
| Assignable to other types | Yes (to anything) | Only to unknown/any |
| Property/method access | Allowed, unchecked | Blocked until narrowed |
| Safety | None | Full — forces a check |
The decisive row is assignability: any flows into a string or number with no complaint, while unknown refuses, forcing an explicit, verified conversion.
Narrowing unknown safely
The idiomatic way to consume unknown is a custom type guard — a function whose return type is a type predicate. It centralizes the runtime checks and tells the compiler what was proven.
interface User {
id: string;
email: string;
}
function isUser(value: unknown): value is User {
return (
typeof value === "object" &&
value !== null &&
"id" in value &&
"email" in value &&
typeof (value as Record<string, unknown>).id === "string"
);
}
function handle(input: unknown) {
if (isUser(input)) {
input.email; // ✅ input is User here
}
}
The value is User predicate means a true result narrows the variable for the rest of the block. For serious validation, a schema library like Zod produces these guards (and the types) for you.
unknown in catch clauses
With strict (specifically useUnknownInCatchVariables), caught errors are typed unknown, because anything can be thrown — not just Error. You must narrow before reading .message.
try {
risky();
} catch (err) {
// err is unknown
if (err instanceof Error) console.error(err.message);
else console.error("Non-error thrown:", err);
}
This prevents the common bug of assuming err.message exists when a string, number, or undefined was thrown instead.
Use
unknownat every boundary where data enters your program — JSON,fetch,localStorage, message events,catch. Validate once at the edge, and the rest of your code can be fully typed and safe.
When any is acceptable
any is occasionally pragmatic: rapidly prototyping, migrating untyped JavaScript, or working around an incorrect third-party type. Even then, prefer the narrowest scope and a // eslint-disable with a reason. The @typescript-eslint/no-explicit-any rule exists precisely to keep these uses rare and deliberate.
Best Practices
- Default to
unknownfor any value whose type you cannot guarantee. - Treat
anyas a code smell; enable@typescript-eslint/no-explicit-any. - Narrow
unknownwithtypeof,instanceof,in, or custom type guards before use. - Validate external data (JSON,
fetch) at the boundary, ideally with a schema library. - Keep
useUnknownInCatchVariableson and narrow errors withinstanceof Error. - Never let
anycross a public API surface — it poisons every caller’s types. - If you must use
any, scope it tightly and document why.