Skip to content
TypeScript ts best-practices 4 min read

unknown vs any

unknown and any are both “top types” — every value is assignable to them — but they behave like opposites once you try to use the value. any switches the type checker off: you can call anything, access any property, and assign it anywhere, with no warnings and no safety. unknown keeps the checker on: you may hold a value of unknown type, but you must prove what it is before doing anything with it. The rule of thumb is simple — when you do not know a value’s type, reach for unknown, and treat any as a last resort you actively avoid. This page contrasts the two and shows how to handle unknown safely.

any disables the checker

Assigning a value to any opts every downstream operation out of type checking. The compiler stops protecting you, and mistakes surface only at runtime.

const data: any = JSON.parse('{"name":"Ada"}');
data.toUpperCase();      // no error — crashes at runtime (it's an object)
data.foo.bar.baz;        // no error — crashes at runtime
const n: number = data;  // no error — silently wrong

None of these lines is flagged. Worse, any is contagious: it spreads through every expression it touches, quietly eroding the safety of code far from the original cast.

unknown keeps the checker on

unknown accepts any value too, but forbids operating on it until you narrow it to something specific. The same mistakes become compile errors.

const data: unknown = JSON.parse('{"name":"Ada"}');

// ❌ Error: 'data' is of type 'unknown'.
data.toUpperCase();

if (typeof data === "object" && data !== null && "name" in data) {
  // narrowed: data is now object with a 'name' key
  console.log((data as { name: string }).name);
}

unknown says “I have a value but no guarantees about it,” which is exactly the honest type for parsed JSON, catch variables, or third-party data. You unlock it only after a type guard proves its shape.

The key differences

Aspectanyunknown
Accepts any valueYesYes
Use without narrowingYes (unchecked)No (compile error)
Assignable to other typesYes (to anything)Only to unknown/any
Property/method accessAllowed, uncheckedBlocked until narrowed
SafetyNoneFull — forces a check

The decisive row is assignability: any flows into a string or number with no complaint, while unknown refuses, forcing an explicit, verified conversion.

Narrowing unknown safely

The idiomatic way to consume unknown is a custom type guard — a function whose return type is a type predicate. It centralizes the runtime checks and tells the compiler what was proven.

interface User {
  id: string;
  email: string;
}

function isUser(value: unknown): value is User {
  return (
    typeof value === "object" &&
    value !== null &&
    "id" in value &&
    "email" in value &&
    typeof (value as Record<string, unknown>).id === "string"
  );
}

function handle(input: unknown) {
  if (isUser(input)) {
    input.email; // ✅ input is User here
  }
}

The value is User predicate means a true result narrows the variable for the rest of the block. For serious validation, a schema library like Zod produces these guards (and the types) for you.

unknown in catch clauses

With strict (specifically useUnknownInCatchVariables), caught errors are typed unknown, because anything can be thrown — not just Error. You must narrow before reading .message.

try {
  risky();
} catch (err) {
  // err is unknown
  if (err instanceof Error) console.error(err.message);
  else console.error("Non-error thrown:", err);
}

This prevents the common bug of assuming err.message exists when a string, number, or undefined was thrown instead.

Use unknown at every boundary where data enters your program — JSON, fetch, localStorage, message events, catch. Validate once at the edge, and the rest of your code can be fully typed and safe.

When any is acceptable

any is occasionally pragmatic: rapidly prototyping, migrating untyped JavaScript, or working around an incorrect third-party type. Even then, prefer the narrowest scope and a // eslint-disable with a reason. The @typescript-eslint/no-explicit-any rule exists precisely to keep these uses rare and deliberate.

Best Practices

  • Default to unknown for any value whose type you cannot guarantee.
  • Treat any as a code smell; enable @typescript-eslint/no-explicit-any.
  • Narrow unknown with typeof, instanceof, in, or custom type guards before use.
  • Validate external data (JSON, fetch) at the boundary, ideally with a schema library.
  • Keep useUnknownInCatchVariables on and narrow errors with instanceof Error.
  • Never let any cross a public API surface — it poisons every caller’s types.
  • If you must use any, scope it tightly and document why.
Last updated June 29, 2026
Was this helpful?