TypeScript any vs unknown — The Answer Reddit Gives Wrong

TypeScript types

Someone asks how to type JSON.parse results. Top comment: “just use any.” That silences the compiler and every caller. The type you want at trust boundaries is unknown: “this value exists, but you must narrow before use.” any means “turn off checking for this and anything it touches.”

Behavioral difference in one snippet

function takeAny(x: any) {
  x.foo.bar(); // ok for TS — runtime may explode
}

function takeUnknown(x: unknown) {
  x.foo.bar(); // error
  if (
    typeof x === "object" &&
    x !== null &&
    "foo" in x &&
    typeof (x as { foo: unknown }).foo === "object"
  ) {
    // still better: use a type guard / zod
  }
}

unknown is a type-safe top type. any is an escape hatch that propagates.

Where unknown belongs

BoundaryPattern
JSON.parseunknown → validate
req.bodyunknown → schema
localStorage.getItem then parseunknown
External widget callbacksunknown params
error in catchunknown (TS 4.4+)
function parseConfig(raw: string): Config {
  const data: unknown = JSON.parse(raw);
  return ConfigSchema.parse(data); // zod/yup/valibot
}

If you refuse a schema library, write a type guard:

function isConfig(v: unknown): v is Config {
  return (
    typeof v === "object" &&
    v !== null &&
    "apiUrl" in v &&
    typeof (v as { apiUrl: unknown }).apiUrl === "string"
  );
}

Where any still appears (narrowly)

  • Migrating a JS file and you need a temporary bridge — track with // TODO and eslint @typescript-eslint/no-explicit-any.
  • Typing third-party modules with broken types — prefer unknown or a minimal local interface over any.
  • Generic libraries that truly accept anything and forward it — even then unknown + generics often wins.
// prefer
function identity<T>(x: T): T { return x; }

// not
function identity(x: any): any { return x; }

Why Reddit’s “cast to any” fix is costly

const el = document.querySelector("#app") as any;
el.innerHTML = userHtml; // also a security smell

You lost null checking, DOM typing, and review signal. Prefer:

const el = document.querySelector("#app");
if (!(el instanceof HTMLElement)) throw new Error("#app missing");
el.replaceChildren(/* safe nodes */);

as unknown as T double assertion

This is the “I know better than the compiler” hammer:

const t = value as unknown as Target;

Use only when you have a proven invariant the type system cannot express — and document it. Prefer refactoring types so a single assertion or guard suffices. Double assertions are any with extra steps.

ESLint defaults that help

{
  "rules": {
    "@typescript-eslint/no-explicit-any": "error",
    "@typescript-eslint/no-unsafe-assignment": "error",
    "@typescript-eslint/no-unsafe-member-access": "error"
  }
}

Teams that ban any but allow unchecked as will cheat. Gate both.

Mental rule

  • unknown: inbound data you do not trust yet.
  • Concrete types: after validation.
  • any: intentional debt with an owner and an expiry.

Gradual adoption

Turn on no-explicit-any as warn first, then error per package. Replace high-churn any at API boundaries before chasing cosmetic locals. Require a one-line reason on every eslint-disable for any. Pairing unknown with a zod (or similar) schema at the boundary deletes entire classes of as casts downstream — invest there before sprinkling generics everywhere.

The wrong Reddit answer optimizes for fewer red squiggles. The right one optimizes for failures at the boundary instead of in production business logic.