Any vs Unknown in TypeScript
Any and unknown both accept every type, but any disables type checking while unknown forces you to narrow the type first. Learn the critical difference and when to use each.
Both any and unknown in TypeScript can hold any value: a string, a number, an object, an array, anything. The difference is what you can do with that value afterward.
any turns off type checking completely, so the compiler lets you call anything on it. unknown keeps type checking on and forces you to prove what the value is before you use it.
Here is the simplest demonstration of the difference:
let a: any = "hello";
let b: unknown = "hello";
a.toUpperCase(); // ok: no checking
b.toUpperCase(); // ErrorThe first line compiles because any silences the compiler entirely, even though calling a method on the wrong runtime value would still crash. The second line fails to compile because unknown refuses to let you call a string method until you prove the value is actually a string.
Object is of type 'unknown'.What Any Does
any is an opt-out from the type system. When you assign a value to an any variable or mark a parameter as any, TypeScript stops checking that code path entirely.
let value: any = "hello";
value = 42; // ok
value = true; // ok
value.toUpperCase(); // ok, but crashes at runtime if value is not a stringEvery line above compiles with no errors or warnings. The compiler trusts you completely, even when a line will clearly fail at runtime because the value is a number instead of a string.
any also spreads through expressions. When you use an any value in a calculation, the result becomes any too, so a single unchecked value can silently disable type checking across an entire function:
let input: any = "42";
let doubled = input * 2; // doubled is any, no type checking here eitherWhen Any Is Acceptable
Despite the risks, any has legitimate uses:
- Gradual migration: when converting a JavaScript file to TypeScript, any lets you add types incrementally.
- Third-party code without types: when a library has no type declarations and you cannot write them yet.
- Escape hatches: in rare cases where the type system cannot express a valid pattern, any lets you move forward.
Use any as a temporary tool, not a permanent solution. For more on migration, see migrate JavaScript to TypeScript step by step.
What Unknown Does
unknown is the safe version of any. It accepts every value but prevents you from using that value until you narrow its type. Think of unknown as a sealed box: you know something is inside, but you must open it and check the contents before you can work with it.
let data: unknown;
data = "hello";
data = 42;
// Both assignments compile because unknown accepts anything.Reading that value back out is where unknown stops you. Every property access and method call fails until the type is proven:
let data: unknown = "hello";
data.toUpperCase(); // Error
data.length; // Errorunknown tells the compiler: "I do not know what this is. Do not let anyone use it without proving its type first."
Narrowing Unknown to a Safe Type
To use an unknown value, you must narrow it. Narrowing means checking the value's runtime type and only using it when the check passes. The flow below shows the only safe path through a typeof check.
Every branch in the diagram starts from the same unnarrowed value and only reaches a usable type after a runtime check confirms it. TypeScript tracks the check and narrows the type inside each matching branch of your code, the same way the diagram narrows toward one outcome.
The most common narrowing tool is typeof:
function processValue(input: unknown): string {
if (typeof input === "string") {
return input.toUpperCase(); // input is string here
}
if (typeof input === "number") {
return input.toFixed(2); // input is number here
}
return "Unsupported type";
}Inside each if block, TypeScript knows the exact type of input because the typeof check already proved it. The compiler allows toUpperCase in the string branch and toFixed in the number branch, but not the other way around, and calling the function confirms the narrowing works as expected:
console.log(processValue("hello")); // "HELLO"
console.log(processValue(42)); // "42.00"For objects, combine typeof with an in check or a custom type guard before accessing a property. For a deeper look at these techniques, see custom type guards in TypeScript.
The Key Difference in Practice
The difference between any and unknown is clearest when a function receives external data with no known shape.
function getLengthAny(value: any): number {
return value.length; // compiles, but crashes if value has no length
}The any version compiles without a single warning and crashes at runtime the moment you call it with a number, because nothing ever checked that value actually has a length property. The unknown version below forces that check before the property access is even allowed:
function getLengthUnknown(value: unknown): number {
if (typeof value === "string" || Array.isArray(value)) {
return value.length; // safe: we proved it has length
}
return 0;
}Both functions do the same job, but only the unknown version can never crash from a type mismatch, because every path either proves the value has a length or falls back to a safe default.
When to Use Each
The table below summarizes when to reach for each type:
| Scenario | Use |
|---|---|
| API response data or JSON.parse() result | unknown |
| User input from a form or external source | unknown |
| Converting a .js file to .ts during migration | any (temporary) |
| Unavoidable type-system limitation | any (rare escape hatch) |
The rule is simple: every value that comes from outside your program should start as unknown. Parse it, validate it, narrow it, and only then treat it as a known type. For more on validation patterns, see runtime validation in TypeScript.
Common Mistakes
Using Any Because Narrowing Unknown Seems Like Extra Work
function handle(data: any) {
console.log(data.name.toUpperCase()); // crashes if data has no name
}The typeof check for unknown takes two lines and prevents a production crash. The extra work is minimal and the safety is permanent.
Narrowing Unknown With an Assertion Without Checking
function handle(data: unknown) {
const name = (data as { name: string }).name; // compiles, but may crash
}Type assertions override the compiler but do not add any runtime safety. Only use an assertion when you have already checked the type through other means, and prefer typeof and type guards over a bare assertion whenever possible.
Forgetting That Unknown Is Not a Runtime Type
let data: unknown = "hello";
console.log(typeof data); // "string" (not "unknown")At runtime, unknown does not exist. The value is whatever JavaScript value it holds, and the unknown type only exists during type checking, erased completely once compilation finishes.
Rune AI
Key Insights
- any disables type checking; unknown forces you to narrow before use.
- any is assignable to anything; unknown is only assignable to unknown and any.
- Narrow unknown with
typeof,instanceof, custom type guards, or assertions. - Use unknown for API responses, parsed JSON, and external input.
- Use any only for gradual migration or rare escape hatches.
Frequently Asked Questions
When should I use any instead of unknown?
Can I assign unknown to a variable of another type?
Does unknown exist at runtime?
Conclusion
Any and unknown are both universal types that accept any value, but they have opposite safety guarantees. Any tells TypeScript to trust you completely and disables all checks. Unknown tells TypeScript not to trust the value at all until you prove what it is. Use unknown for API responses, user input, parsed JSON, and any value that comes from outside your type system. Reserve any for migration paths and rare escape hatches where you genuinely need to bypass the type checker.
More in this topic
TypeScript Compiler Explained
The TypeScript compiler (tsc) turns .ts files into .js files while checking types. Learn what it does, how to use it, and how it fits your workflow.
Set Up ESLint for TypeScript
Install and configure ESLint with typescript-eslint to catch bugs and enforce consistent code style in your TypeScript project.
Pick Utility Type in TypeScript
Pick creates a new type by selecting only the properties you name from an existing type. Use it to build focused types for specific views, API responses, or function parameters.