Skip to content
TypeScript ts basic-types 4 min read

The any Type

The any type is TypeScript’s escape hatch: it tells the compiler to stop checking a value entirely. A value typed any can be assigned to or from anything, called, indexed, and treated as any shape with zero complaints. That power makes it tempting for quick fixes, but it silently disables the very guarantees TypeScript exists to provide — and it tends to spread. This page explains how any behaves, how it appears implicitly, the bugs it hides, and why unknown is almost always the better choice.

What any does

Annotating a value as any removes it from the type system. Every operation on it compiles, regardless of whether it would succeed at runtime.

let value: any = "hello";

value.toFixed(2);     // ✅ compiles — runtime TypeError
value();              // ✅ compiles — runtime TypeError
value.foo.bar.baz;    // ✅ compiles — runtime TypeError
const n: number = value; // ✅ compiles — no check at all

None of these lines are flagged, yet most crash at runtime. any is bidirectionally assignable: anything goes into an any, and an any flows out into any other type. The compiler effectively trusts you completely, which is exactly why it is dangerous.

Implicit any

You do not have to write any to get it. When TypeScript cannot infer a type and no annotation is given, it falls back to an implicit any — unless noImplicitAny is enabled.

// With noImplicitAny: false (legacy / loose config)
function parse(input) {        // `input` is implicitly any
  return input.trim().split(",");
}

// With noImplicitAny: true (recommended, part of `strict`)
function parse(input) {
  // ❌ Error: Parameter 'input' implicitly has an 'any' type.
  return input.trim().split(",");
}

The strict family in tsconfig.json turns noImplicitAny on, forcing you to annotate function parameters and other untyped positions. Keep it enabled so accidental any cannot slip in unnoticed.

{
  "compilerOptions": {
    "strict": true,        // enables noImplicitAny and more
    "noImplicitAny": true
  }
}

any is infectious

The real cost of any is propagation. The result of any expression that touches an any is itself any, so a single untyped value can erase type safety across a whole chain of code.

const data: any = JSON.parse('{"count":5}');

const count = data.count;   // count: any
const doubled = count * 2;  // doubled: any
const label = `${doubled}`; // label: any — string-ness lost

label.toFixed();            // ✅ compiles — runtime TypeError

Even though label is obviously a string, its type is any because it descends from data. This infectiousness is why one careless any at an API boundary quietly degrades everything downstream.

How any hides real bugs

Because any disables checking, it masks genuine errors that TypeScript would otherwise catch at compile time.

interface User {
  id: number;
  name: string;
}

const user: any = fetchUser();

console.log(user.naem);   // ✅ compiles — typo, undefined at runtime
user.id = "not a number"; // ✅ compiles — wrong type, no error

// Typed version catches both:
const typed: User = fetchUser();
// ❌ Error: Property 'naem' does not exist on type 'User'.
console.log(typed.naem);

The any version compiles cleanly while shipping a typo and a type violation. The typed version stops both at build time. This is the trade you make every time you reach for any.

any vs unknown

When you must accept a value whose type you don’t yet know, prefer unknown. It is the type-safe counterpart of any: you can still assign anything to it, but you cannot use it until you narrow it.

Aspectanyunknown
Assign anything to itYesYes
Assign it to other typesYes (unsafe)No (must narrow first)
Access properties / call itYes, uncheckedNo, until narrowed
Type checking on the valueDisabledFully enforced
Infectious through expressionsYesNo
Recommended for new codeAvoidYes
function handle(input: unknown) {
  // ❌ Error: 'input' is of type 'unknown'.
  input.toFixed(2);

  if (typeof input === "number") {
    input.toFixed(2); // ✅ narrowed to number
  }
}

unknown forces a deliberate check before use, giving you the flexibility of any without surrendering safety. See The unknown Type and unknown vs any.

If you must use any, isolate it to the smallest possible scope and convert it to a real type immediately — for example, validate parsed JSON into a typed shape rather than letting any leak into your domain logic.

Legitimate (rare) uses

any is occasionally justified: migrating large untyped JavaScript incrementally, interoperating with poorly typed third-party libraries, or in throwaway prototype code. In these cases prefer a targeted assertion or a // @ts-expect-error over a blanket any.

// Migration: silence one line instead of typing the whole value as any
// @ts-expect-error legacy module has no types yet
import legacy from "./legacy.js";

Lock down accidental any with the lint rule @typescript-eslint/no-explicit-any, and reserve the escape hatch for places you have consciously chosen.

Best Practices

  • Enable strict (and therefore noImplicitAny) so implicit any is rejected.
  • Prefer unknown whenever you need to accept a value of an undetermined type.
  • Treat every any as technical debt; narrow it to a concrete type as soon as possible.
  • Confine unavoidable any to the smallest scope and validate it at the boundary.
  • Turn on @typescript-eslint/no-explicit-any to flag intentional uses for review.
  • Remember any is infectious — one leak can silently disable downstream checks.
  • Use targeted type assertions instead of any for one-off conversions.
Last updated June 29, 2026
Was this helpful?