What you'll learn
Quick Answer
Use unknown, not any, for values whose type you don't know yet, like API responses or user input. any switches off type checking, so the compiler stays quiet while bugs slip through to runtime. unknown accepts any value too, but forces you to check its type (narrow it) before you use it, so mistakes get caught at compile time. Reach for any only as a last resort during tricky migrations.
TypeScript any vs unknown: the quick picture
When you learn TypeScript, sooner or later you hit a value whose type you just don't know. The JSON from an API, something read from localStorage, or text a user typed into a form. TypeScript gives you two escape hatches for these situations: any and unknown. They look similar, but they behave very differently, and picking the wrong one quietly removes the safety you came to TypeScript for in the first place.
This guide to typescript any vs unknown shows exactly what the compiler lets you do with each, using small examples you can paste straight into the TypeScript Playground. It ends with a clear recommendation. If you want the whole language from the ground up, our free TypeScript course walks through the type system step by step.
What any actually does
any is TypeScript's "trust me, turn off the checks" type. When a value is any, the compiler stops looking at it. You can do anything to it, and every line below compiles with zero errors:
let value: any = "hello";
value.foo.bar; // compiles (crashes at runtime)
value(); // compiles (crashes at runtime)
value * 10; // compiles
value.toUpperCase(); // compiles
const n: number = value; // compiles — any flows into number
const s: string = value; // compiles — any flows into stringNotice the problem. value holds the string "hello", yet the compiler happily lets you call it like a function and read .foo.bar off it. None of that is caught. The errors only show up when the code runs and your app throws a TypeError in the browser, often far from where the real mistake was.
Worse, any is contagious. Because it assigns freely into other types, one any can spread through a whole chain of variables and silently switch off checking in code that looks perfectly safe.
What unknown actually does
unknown is the type-safe counterpart. It is the "top type": every value is assignable to unknown, so it can hold anything, just like any:
let value: unknown;
value = "hello"; // ok
value = 42; // ok
value = true; // ok
value = { a: 1 }; // okThe difference is the other direction. TypeScript refuses to let you use an unknown value until you prove what it is. Every line below is a compile-time error:
let value: unknown = "hello";
value.foo.bar; // Error: 'value' is of type 'unknown'
value(); // Error: 'value' is of type 'unknown'
value * 10; // Error: 'value' is of type 'unknown'
value.toUpperCase(); // Error: 'value' is of type 'unknown'
const n: number = value; // Error: 'unknown' is not assignable to 'number'Same values, same operations, but now every risky line is rejected before the code ever runs. That is the whole point of unknown: it accepts any input but will not let you treat it as something specific until you check.
any vs unknown, side by side
Here is the same story as a table. For any, a "Yes" mostly means "the compiler stays silent", which is exactly what makes it risky.
| Behaviour | any | unknown |
| Can hold a value of any type | Yes | Yes |
| Access properties without checking first | Yes | No |
| Call it or do math without checking | Yes | No |
| Assign it to other types with no cast | Yes | No |
| Catches typos and bad calls at compile time | No | Yes |
| Forces you to narrow before use | No | Yes |
Both are equally flexible about what they can store. They differ entirely in what they let you do with that stored value.
How to use unknown: narrowing
So how do you actually use an unknown value? You narrow it. You write a check that proves its type, and inside that check TypeScript treats it as the narrower type. The simplest tool is typeof:
function printLength(value: unknown) {
if (typeof value === "string") {
// Inside this block, value is a string
console.log(value.length); // ok
console.log(value.toUpperCase()); // ok
} else {
console.log("Not a string");
}
}Outside the if, writing value.length would be an error. Inside it, TypeScript has narrowed value to string, so string methods are allowed and safe. You get the flexibility of accepting anything, plus the safety of checking before use.
Other everyday narrowing tools include Array.isArray(value), value instanceof Date, and checking typeof value === "object" && value !== null before you read any properties off an object.
The best use: untyped and external data
The place unknown really pays off is data from outside your program: fetch responses, JSON files, localStorage, message events. You genuinely don't know its shape, so unknown is the honest type. Then you write a type guard to validate it once, and everything after that point is fully typed.
type User = { id: string; name: string };
// A type guard: returns true only when data really is a User
function isUser(data: unknown): data is User {
return (
typeof data === "object" &&
data !== null &&
"id" in data &&
"name" in data &&
typeof (data as Record<string, unknown>).id === "string" &&
typeof (data as Record<string, unknown>).name === "string"
);
}
async function loadUser(id: string): Promise<User | null> {
const res = await fetch(`/api/users/${id}`);
const data: unknown = await res.json();
if (isUser(data)) {
return data; // data is now a fully-typed User
}
return null; // shape didn't match — handle it safely
}Because data starts as unknown, TypeScript won't let you write data.name by accident before checking. After isUser(data) passes, data is a real User and autocomplete works. If someone changes the API and the shape breaks, your return null branch handles it instead of your UI crashing on undefined.
One nice detail: the return type of res.json() is actually any. Writing const data: unknown = await res.json(); is a deliberate, useful move. You are upgrading a loose any back into a strict unknown and forcing yourself to validate.
When is any actually okay?
any isn't evil, it's just blunt. There are a few honest uses:
- Migrating old JavaScript. When you convert a large JS codebase to TypeScript, sprinkling
anylets you compile now and tighten types later. - Truly dynamic code where even you can't describe the type, and the extra safety isn't worth the effort.
- Quick prototypes that you plan to throw away.
Even then, try unknown first and reach for any only when narrowing genuinely gets in your way. Two gotchas to remember:
- any is contagious; unknown is not. An
anyvalue flows into typed variables and disables checks around it. Anunknownvalue can't be assigned to a typed variable without a check, so the uncertainty stays contained. - Turn on
noImplicitAny. With this compiler flag (part ofstrictmode), TypeScript warns when a type silently becomesany, so you notice instead of losing safety by accident.
Which should you use?
Default to unknown. It gives you the same flexibility as any, because it can hold any value, but it makes you prove the type before you use it, so bugs are caught at compile time instead of in production. Use it for every value whose type you don't yet know: API data, form input, third-party payloads, anything crossing the boundary into your program.
Keep any for rare cases: mid-migration code, throwaway prototypes, or spots where narrowing truly isn't worth the effort. If you catch yourself typing any out of habit, pause. unknown plus a small typeof check is usually just as quick to write and far safer.
Rule of thumb: reach forunknownby default, and treat everyanyas a TODO you should eventually replace.
Want to go deeper into type guards, generics, and strict mode? Work through our free TypeScript course and practise these patterns on real code.
Frequently Asked Questions
What's the main difference between any and unknown?
Both can hold a value of any type. The difference is what you can do next. With any, the compiler lets you do anything, so mistakes slip through to runtime. With unknown, you must check the type (narrow it) before you use the value, so mistakes are caught at compile time.
Can I assign an unknown value to a typed variable?
Not directly. const n: number = someUnknown; is an error. You first narrow it, for example with if (typeof someUnknown === "number"), and inside that block you can assign it. This is exactly the safety unknown is designed to give you.
Do any and unknown change the compiled JavaScript?
No. Both are compile-time only. TypeScript erases all types when it compiles to JavaScript, so any and unknown produce identical output and have zero runtime cost. They only affect which errors the compiler shows you while you write code.
What should I use for the error in a catch block?
Use unknown. In modern TypeScript the caught error is typed as unknown by default, because a thrown value can be anything, not just an Error. Check it before using it, for example if (err instanceof Error) console.log(err.message);.
How do I turn an unknown into a specific object type safely?
Write a type guard, a function returning data is User that checks the fields at runtime using typeof, the in operator, and null checks. Once the guard passes, TypeScript treats the value as that type with full autocomplete. This is the standard way to validate API responses.
