any and unknown both accept every value. The difference is what you can do with the value afterwards: any lets you do anything and checks nothing, while unknown lets you do almost nothing until you prove what the value is.
Without the @ts-expect-error line, u.toUpperCase() is compile error TS18046. The typeof check narrows u to string, and inside that block every string method is available.
any vs unknown at a Glance
any | unknown | |
|---|---|---|
| Accepts any value | yes | yes |
Assign it to a string, number, ... | yes, unchecked | no (TS2322) |
| Read a property, call a method | yes, unchecked | no (TS18046) |
| Call it as a function | yes | no |
Arithmetic and comparison (x * 2, x + 1, x < 5) | yes | no (TS18046) |
| Needs a check before use | no | yes (typeof, instanceof, in, a type guard) |
| Effect on type checking | switched off for that value and everything it touches | kept on |
In type theory terms, unknown is the top type: every type is assignable to it, and it is assignable only to unknown and any. any is an escape hatch that is assignable both ways, to everything except never.
any Switches Off Type Checking
A value typed any is trusted blindly. The compiler accepts typos, wrong types and missing properties, and the mistakes turn up at runtime instead.
The output shows the problem: a variable annotated number holds a string, and the last line throws Cannot read properties of undefined (reading 'city'). any also spreads. user.name is any, so any value computed from it is any, and one untyped value can switch off checking far from where it entered.
unknown Makes You Check First
With unknown, the compiler refuses every operation until the code narrows the value. Narrowing uses ordinary JavaScript checks, and inside each branch the value has the checked type.
The value === null check has to come before the object check because typeof null is "object". After "id" in value, TypeScript knows the object has an id property, typed unknown, since nothing says what it holds. String() turns it into text explicitly; to use it as a number you would check it with typeof first.
You can also skip the check with an assertion, value as string, and the compiler accepts it. That is a promise with no runtime check behind it, so prefer a real check; see type assertions.
Validating JSON with unknown
JSON.parse is declared to return any, so its result silently disables checking. Annotate the result as unknown and write a type guard that checks the shape before the rest of the code trusts it.
The first input prints dark at 14px, the second is rejected because fontSize is missing. With any, the second input would have flowed through as a Settings with fontSize undefined. For large schemas, a validation library does the same job and derives the type for you.
noImplicitAny
any does not only come from people writing it. A parameter with no annotation and no context to infer from would be any too. The noImplicitAny option, which strict turns on, reports that as an error:
index.ts(2,17): error TS7006: Parameter 'x' implicitly has an 'any' type.
The fix is an annotation, function double(x: number). Writing x: any explicitly also compiles, which is the point: any stays visible in the code and can be searched for and reviewed.
Where any Still Sneaks In
Even under strict, these produce any without the word appearing in your code:
| Source | What you get | What to do |
|---|---|---|
JSON.parse(text) | any | annotate as unknown, then validate |
response.json() with the browser (DOM) types | Promise<any> | same as JSON.parse (Node's own fetch types already return Promise<unknown>) |
| A package with no type definitions | any for its imports (with an error unless you add a declaration) | install @types/... or write a .d.ts |
value as any | any | use a type guard or a precise assertion |
catch (e) with useUnknownInCatchVariables off | any | strict makes it unknown; keep it that way |
The catch variable is unknown under strict because anything can be thrown, not only Error objects. Narrow it with e instanceof Error before reading e.message.
When any Is Acceptable
any is not forbidden, but each one is a spot the compiler no longer protects. Reasonable uses:
- Migrating a JavaScript codebase, where
anymarks what is not typed yet. - Code the type system cannot express well, kept small and behind a typed function signature.
- Test code that deliberately passes bad data.
For everything else, unknown covers the same "I do not know this type" case while keeping the checks. Many teams enforce this with the @typescript-eslint/no-explicit-any lint rule. A related type, Record<string, unknown>, is the usual choice for "some object with unknown values".
Frequently Asked Questions
What is the difference between any and unknown in TypeScript?
Both accept any value. With any you can do anything with the value (read properties, call it, assign it to a number) and the compiler checks nothing. With unknown you can do almost nothing until you narrow it with a check such as typeof x === "string". unknown keeps type checking on, which is why it is the safer choice.
When should I use unknown instead of any?
Whenever a value's type is not known at compile time: parsed JSON, data from a network response, a caught error, the input of a validation function. Type it as unknown and narrow it. Reach for any only for short-term migration work or code the type system cannot describe.
What does "Object is of type 'unknown'" mean?
Errors TS18046 ('x' is of type 'unknown') and TS2571 (Object is of type 'unknown', used when the value is not a plain name, such as load().id) mean you used an unknown value as if it had a specific type, for example by reading a property or calling a method. Check the type first (typeof, instanceof, Array.isArray, in, or a type guard function), and use the value inside the narrowed branch.
Why does JSON.parse return any?
Its declaration in the standard library says parse(text: string, ...): any, because the compiler cannot know what a string contains. The result silently switches off checking for everything it touches. Annotate it instead: const data: unknown = JSON.parse(text), then validate it before use.
What is noImplicitAny?
A compiler option, part of strict, that reports an error when a declaration would silently get the type any because it has no annotation and nothing to infer from: TS7006 for a parameter, TS7005 or TS7034 for a variable whose type cannot be worked out. It stops any from appearing without anyone writing it. Explicit any is still allowed.