skip to content

any vs unknown

any switches type checking off and quietly infects everything it touches; unknown accepts anything but forces you to prove what it is before using it. This is the single most common TypeScript interview question, and the expected answer is that unknown is the safe default for untrusted input.

part ofTypeScriptoverview, primer and where to startread it →
on this pageshow

questions

4

In TypeScript, what is the difference between the `any` and `unknown` types, and which one would you give to a value returned by JSON.parse?

level: juniorimportance: must knowfreq 88%

answer

  1. both accept anything going in
  2. the difference is coming out
  3. top type versus checking switched off
  4. assignable only to unknown and any
  5. narrow or assert before using it

basics

~20 s

any turns off type checking for a value and everything derived from it; unknown accepts any value but permits almost no operations until you narrow or assert it. Use unknown for JSON.parse output, then validate before use.

solid answer

~40 s

Both accept any value going in; they differ on the way out. `any` disables checking: you can read any property, call it, index it, and assign it to a variable of any other type, and everything derived from it is `any` too — so a mistake surfaces at runtime instead of in the editor. `unknown` is the top type: every type is assignable to it, but it is assignable only to `unknown` and `any`, and you can do almost nothing with it until you narrow it with a runtime check or assert it with `as`. The standard library declares `JSON.parse` as returning `any`, so I would immediately annotate the result as `unknown` and validate its shape before use. That turns a silent, deferred runtime failure into a compile error that forces the validation step.

code

typescript · 13 lines
typescript
function widthOf(raw: string) {
  const loose: any = JSON.parse(raw);
  return loose.size.width; // compiles; may throw at runtime
}

function safeWidthOf(raw: string) {
  const parsed: unknown = JSON.parse(raw);
  // return parsed.size.width;  // Error: 'parsed' is of type 'unknown'
  if (typeof parsed === "number") {
    return parsed;
  }
  return 0;
}

go deeper

for a junior

Be ready to state plainly that any switches off checking while unknown forces you to check first, and to name unknown as the right type for parsed JSON.

for a middle

Explain the assignability rules in both directions: any goes into everything except never, unknown comes out only into unknown and any. Name the operations still allowed on an unknown.

for a senior

Show where the choice pays off in production: the boundary types the standard library declares as any, and how a one-word annotation forces a validation step that prevents a class of runtime failures.

for a principal

Own the policy question — what an any is allowed to cost, where the team permits it, and how you keep unknown-at-the-boundary from degenerating into assertions that check nothing.

## The problem both types solve TypeScript's type layer exists only while the compiler runs — the emitted JavaScript has the types stripped out entirely. So the checker can reason only about what you told it, and some values genuinely have no known type at compile time: JSON arriving off the wire, a value from a library that ships no type declarations, raw user input. TypeScript offers two types for "I do not know what this is", and they sit at opposite ends of the safety scale. ## any: checking switched off `any` is less a type than an instruction to stop checking. On a value typed `any` you may read or write any property, call it, index into it, use it in arithmetic, and assign it to a variable of almost any other type — `any` is assignable to every type except `never`. Nothing is reported at any step: ```ts const v: any = "hello"; const n: number = v; // no error at all n.toFixed(2); // no error; throws TypeError at runtime ``` The annotation `number` is believed, not verified. The compiler has been told to trust you, so the mistake escapes the type system completely and shows up as a runtime crash somewhere downstream. ## unknown: the top type `unknown` is the *top type* of the system: every type is assignable to `unknown`, and `unknown` is assignable only to `unknown` and `any`. Anything goes in; nothing comes out unproven. Reading a property, calling the value, indexing it, or doing arithmetic on it are all errors ("'v' is of type 'unknown'"). A useful shorthand: `unknown` is what `any` should have been if TypeScript had been designed for safety rather than for gradual adoption of existing JavaScript. A few operations are still legal on an `unknown`, because they are safe for every possible value: assigning it to another `unknown` or to `any`, comparing it with `===`/`!==`, applying `typeof`, using it in a truthiness test, and passing it where a parameter is typed `any` or `unknown` (which is why `console.log(v)` and `JSON.stringify(v)` are fine). ```ts const v: unknown = JSON.parse(input); // v.trim(); // Error: 'v' is of type 'unknown' if (typeof v === "string") v.trim(); // allowed here ``` Two small facts round out the picture: in a union, `unknown` absorbs everything (`unknown | string` is just `unknown`), while in an intersection it disappears (`unknown & string` is `string`). And `keyof unknown` is `never` — there is no key you may assume exists. ## Getting a value out of unknown There are exactly two doors. The first is a runtime check the compiler understands, which narrows the type inside that branch. The second is a type assertion, `value as User`, which performs **no** check at all — it is a promise to the compiler that you are right. Assertions do not convert anything; they only silence the checker, so a wrong assertion moves the failure back to runtime, exactly where `any` left it. Prefer the check; reserve the assertion for cases you have already validated. ## Why unknown is the right default at the boundary The places wrong data actually arrives are precisely the places the standard library hands you `any`: `JSON.parse` is declared as returning `any`, and a DOM `Response`'s `json()` method returns `Promise<any>`. If you let that flow onward, a typo like `user.naem` is never reported, and a payload whose shape changed on the server produces `undefined is not a function` in production. Annotating the result `unknown` costs one word and makes the compiler demand the validation you should have written anyway. ## Where any is still defensible `any` is not forbidden. Genuinely dynamic code, a half-finished migration, or a piece of type gymnastics whose cost exceeds its benefit can all justify it. The discipline is to keep it local and deliberate: a commented `any` inside one function is a very different thing from `any` as the return type of a shared helper, from which it spreads through every caller. ## The line to remember `any` means the compiler stops asking; `unknown` means the compiler keeps asking until you answer. And because types are erased, swapping one for the other changes not a single byte of emitted JavaScript — it changes only which mistakes get caught before you ship.

  • How does typing a value `unknown` differ from typing it `object`?
    `object` means "any non-primitive", so strings, numbers, booleans, `null` and `undefined` are rejected on the way in — `unknown` accepts all of them. On the way out they are similar: reading an arbitrary property off an `object` is still an error, because `object` promises no members. Use `unknown` when you truly know nothing, `object` when you at least know it is not a primitive.
  • Is `any` assignable to every type?
    To every type except `never`. That single exception matters because `never` is the bottom type — it has no values, so nothing can be assigned to it, not even `any`. Everything else, including tightly constrained unions and literal types, accepts an `any` silently, which is why one `any` can defeat annotations far from where it was introduced.
  • What happens to `unknown` inside a union or an intersection?
    It absorbs in unions and vanishes in intersections: `unknown | string` reduces to `unknown`, while `unknown & string` reduces to `string`. That follows from `unknown` being the top type — the union with the widest type is that type, and intersecting with "could be anything" adds no constraint. It is a useful sanity check when a computed type unexpectedly collapses to `unknown`.

saying these in an interview costs you the question

  • Says unknown is just any with a nicer name
  • Thinks assigning unknown to a string needs no check
  • Believes any performs a runtime check or conversion
  • Uses as to escape unknown and calls that validation

context

open as a page

A value typed `any` in TypeScript is often called contagious. What exactly happens to the types of expressions derived from it, and which checks does the compiler stop performing?

level: middleimportance: must knowfreq 66%

basics

~20 s

A value typed any makes every expression derived from it any as well — property reads, calls, indexing, awaits — so whole branches of code stop being checked, and the failure surfaces at runtime far from the original annotation.

open as a page

In TypeScript, what is an "implicit any", and in which code positions does the compiler fall back to it?

level: middleimportance: should knowfreq 54%

basics

~20 s

An implicit any is the type the compiler falls back to when it can infer nothing — typically an unannotated parameter, an uninitialised member, or an untyped import. The noImplicitAny option turns those silent fallbacks into errors, and it is on under strict.

open as a page

Values entering a TypeScript service from JSON.parse, HTTP response bodies and untyped third-party modules all arrive typed as `any`. How do you keep that `any` from spreading into the rest of the codebase?

level: seniorimportance: should knowfreq 42%

basics

~20 s

Type each boundary value as unknown at the point it enters, then validate it into a real type inside one wrapper per boundary, so no any crosses into application code. Assertions do not count — they check nothing at runtime.

open as a page