TypeScript was designed to give strong type safety while keeping the flexibility of JavaScript. The any type often becomes a quick escape hatch, but it removes all compile-time checks.
To solve this problem, TypeScript introduced unknown, a type that behaves similarly to any (it can accept any value), but with safer restrictions.
Let’s explore what unknown is, how it differs from any, and why it should be your default choice when the type is not yet known.
What is unknown in TypeScript?
unknownis the type-safe counterpart ofany.- Like
any, it can hold any value. - Unlike
any, you cannot directly use the value without narrowing its type first.
let value: unknown;
value = 42; // OK
value = "hello"; // OK
value = { x: 1 }; // OK
// But direct usage is not allowed:
console.log(value.toUpperCase());
// ❌ Error: Object is of type 'unknown'
To use an unknown value, you must check or assert its type first:
if (typeof value === "string") {
console.log(value.toUpperCase()); // ✅ Safe
}
any vs unknown in Type Hierarchy
In TypeScript’s type system, every type is connected by subtype and supertype relations.
anyis both a supertype and subtype of all types.- It can be assigned to anything, and anything can be assigned to it.
- This is why
anyis dangerous — it bypasses the type system.
unknownis a supertype of all types, but it is not a subtype of anything except itself andany.- You can assign any value to
unknown. - But you cannot assign
unknownto other types without explicit checking.
- You can assign any value to
Example:
let val: unknown;
val = 123; // OK
val = "text"; // OK
let str: string;
// str = val; // ❌ Error: unknown not assignable to string
str = val as string; // ✅ Explicit cast required
This restriction prevents silent bugs.
Example: Using unknown in API Responses
Imagine you fetch JSON from an API. The shape of the data is not guaranteed:
function parseApiResponse(json: string): unknown {
return JSON.parse(json);
}
const result = parseApiResponse('{"id": 1, "name": "Alice"}');
// result.id ❌ Error (unknown)
// Must validate first:
if (typeof result === "object" && result !== null && "name" in result) {
console.log((result as { name: string }).name); // ✅ Safe
}
With any, this would compile but might crash at runtime. With unknown, you’re forced to validate before usage.
unknown vs any – Comparison Table
| Feature | any | unknown |
|---|---|---|
| Accepts all values | ✅ Yes | ✅ Yes |
| Assignable to all types | ✅ Yes | ❌ No (only to any and unknown) |
| Allows arbitrary property access | ✅ Yes | ❌ No (requires type check) |
| Allows arbitrary function calls | ✅ Yes | ❌ No (requires type check) |
| Safe by default | ❌ No | ✅ Yes |
| Subtype relations | Subtype and supertype of all types | Only supertype of all types (subtype of any and itself) |
| Best use case | Quick prototyping, legacy JS migration (not recommended long term) | When the type is unknown yet must be validated safely |
Best Practices with unknown
✅ Use unknown instead of any whenever you don’t know the type up front.
✅ Perform type narrowing with typeof, instanceof, or custom type guards.
✅ Use unknown for API responses, dynamic inputs, or third-party data.
✅ Avoid using any in strict TypeScript projects — enforce with ESLint:
"rules": {
"@typescript-eslint/no-explicit-any": "error"
}
Conclusion
anyremoves all type safety and should be avoided in production code.unknowngives the same flexibility but forces type safety at usage time.- In TypeScript’s type system:
anyis both supertype and subtype.unknownis only a supertype (cannot silently flow into stricter types).
👉 If you ever feel the urge to use any, use unknown instead — it’s the safe analogue that will protect your project from subtle runtime errors.