Skip to main content

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

  • any is a blank check: call any method, access any property, no questions asked
  • unknown is a locked box: you must prove what is inside before TypeScript lets you touch it
  • any disables type checking; unknown keeps it on and demands type guards
  • Default for "I don't know the type yet": unknown. Use any only when deliberately bypassing TypeScript

Quick example

typescript
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 in catch blocks, parameters from external input
  • any: 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

Aspectanyunknown
Type checkingDisabledEnabled
Call methods / access propertiesYes, no errorsNo, error until narrowed
Assign to other typed variablesYes, freelyNo, requires type guard
Catches mistakes at compile timeNoYes
Requires type guardsNoYes
When to useDeliberate escape hatchDefault 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"

typescript
// 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

typescript
// 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+

typescript
// 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

typescript
// 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 unknown until validated by middleware
  • JSON.parse(): standard lib returns any, but wrapping results in unknown is safer in practice
  • Redux: action payloads often typed as unknown, narrowed via discriminated unions inside reducers
  • React event handlers: event.target must be narrowed before accessing specific properties
  • Zod / io-ts: both accept unknown input 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

typescript
// 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

typescript
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 runtime

any 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 ready
Premium

A concise answer to help you respond confidently on this topic during an interview.

Finished reading?