Differences between any and unknown in TypeScript
any and unknown are both types that accept any value in TypeScript, but only unknown forces you to verify the type before using it.
Theory
TL;DR
anyis a blank check: call any method, access any property, no questions askedunknownis a locked box: you must prove what is inside before TypeScript lets you touch itanydisables type checking;unknownkeeps it on and demands type guards- Default for "I don't know the type yet":
unknown. Useanyonly when deliberately bypassing TypeScript
Quick example
function process(value: any) {
value.toUpperCase(); // No error - but crashes if value is a number
}
function processSafe(value: unknown) {
value.toUpperCase(); // Error: Object is of type 'unknown'
if (typeof value === 'string') {
value.toUpperCase(); // OK - type narrowed to string
}
}processSafe forces you to check first. process trusts you blindly, and that trust can crash at runtime.
Key difference
any tells TypeScript: "stop checking this value entirely." You can call any method, assign it anywhere, pass it to anything. unknown says: "I don't know what this is." You can store it and pass it around, but you cannot call methods or read properties until you prove the type with a type guard. One disables the type system; the other works with it.
When to use
unknown: API responses,JSON.parse()results, error objects incatchblocks, parameters from external inputany: integrating untyped third-party libraries, temporary workarounds during migration (document it when you do)- Neither: if you can define the actual type, always do that
Comparison table
| Aspect | any | unknown |
|---|---|---|
| Type checking | Disabled | Enabled |
| Call methods / access properties | Yes, no errors | No, error until narrowed |
| Assign to other typed variables | Yes, freely | No, requires type guard |
| Catches mistakes at compile time | No | Yes |
| Requires type guards | No | Yes |
| When to use | Deliberate escape hatch | Default for unknown types |
How the compiler handles this
TypeScript treats any as an opt-out marker: wherever it appears, the compiler skips all type validation for that value in both directions. unknown is the opposite - it accepts any value but blocks access to it until you narrow the type. At runtime both are erased to plain JavaScript, so there is zero performance difference between them.
Common mistakes
Mistake 1: reaching for any when you mean "I don't know the type yet"
// Wrong
function handleData(data: any) {
return data.value * 2; // Crashes silently if data is a string
}
// Correct
function handleData(data: unknown) {
if (typeof data === 'object' && data !== null && 'value' in data) {
const val = data.value;
if (typeof val === 'number') return val * 2;
}
throw new Error('Invalid data structure');
}Mistake 2: letting any spread through function signatures
// Wrong - any in, any out, no type checking anywhere
function process(input: any): any {
return input.transform();
}
// Correct - narrow at the boundary, return a concrete type
function process(input: unknown): string {
if (typeof input === 'string') return input.toUpperCase();
throw new Error('Expected string');
}Mistake 3: forgetting that catch errors are unknown in TypeScript 4.0+
// Wrong
try {
doSomething();
} catch (e) {
console.log(e.message); // Error: e is of type unknown
}
// Correct
try {
doSomething();
} catch (e) {
if (e instanceof Error) {
console.log(e.message); // Safe
}
}Mistake 4: using any in object properties as a shortcut
// Wrong - the entire property tree becomes untyped
interface Config {
settings: any;
}
// Better - keys are strings, values must be narrowed before use
interface Config {
settings: Record<string, unknown>;
}I replaced every any with unknown across one codebase and TypeScript surfaced three bugs in the first hour that had been hiding in production for months.
Real-world usage
- Express: request body is
unknownuntil validated by middleware JSON.parse(): standard lib returnsany, but wrapping results inunknownis safer in practice- Redux: action payloads often typed as
unknown, narrowed via discriminated unions inside reducers - React event handlers:
event.targetmust be narrowed before accessing specific properties - Zod / io-ts: both accept
unknowninput and return a typed result after validation
Follow-up questions
Q: Can you assign unknown to any and vice versa?
A: Yes to both. any accepts everything, and unknown also accepts any value on assignment. But you cannot assign unknown to a concrete type like string without a type guard first.
Q: What is the difference between unknown and a generic type parameter <T>?
A: unknown means "some type, I don't know which - prove it before use." A generic <T> means "some specific type the caller decides." Generics preserve type information through the call; unknown drops it until you narrow.
Q: What changed in TypeScript 4.0 regarding these types?
A: The catch clause variable became unknown by default with useUnknownInCatchVariables, enabled automatically in strict mode from TypeScript 4.4. Before that, caught errors were any, so code without guards in catch blocks used to compile cleanly.
Q: When would you use unknown in a generic constraint instead of a type parameter?
A: When you want to force the caller to supply a type guard. function validate<T>(value: unknown, guard: (v: unknown) => v is T): T is safer than function validate<T>(value: T): T because it explicitly handles unknown input and requires a guard to produce a typed result.
Examples
API response handling with a type guard
// Using any - no safety at all
async function fetchUser(id: string): Promise<any> {
const response = await fetch(`/api/users/${id}`);
return response.json();
}
const user = await fetchUser('123');
console.log(user.email); // No TypeScript error, but could be undefined
// Using unknown - safe
async function fetchUserSafe(id: string): Promise<unknown> {
const response = await fetch(`/api/users/${id}`);
return response.json();
}
function isUser(val: unknown): val is { id: string; email: string } {
return (
typeof val === 'object' &&
val !== null &&
'id' in val &&
'email' in val &&
typeof (val as Record<string, unknown>).id === 'string' &&
typeof (val as Record<string, unknown>).email === 'string'
);
}
const data = await fetchUserSafe('123');
if (isUser(data)) {
console.log(data.email); // Fully type-safe
}The type guard runs once and gives you a typed value everywhere you need it. That is a better trade-off than skipping the check entirely.
any bypasses generic constraints; unknown does not
function processArray<T extends string>(arr: T[]): void {
arr.forEach(item => console.log(item.toUpperCase()));
}
const anyValue: any = [1, 2, 3];
processArray(anyValue); // No TypeScript error - crashes at runtime
const unknownValue: unknown = [1, 2, 3];
processArray(unknownValue); // TypeScript error - caught before runtimeany passes through generic constraints without a word. unknown does not. This is one of the sharper edges of any in shared or library code.
Short Answer
Interview readyA concise answer to help you respond confidently on this topic during an interview.