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.
| Aspect | any | unknown |
|---|---|---|
| Assign anything to it | Yes | Yes |
| Assign it to other types | Yes (unsafe) | No (must narrow first) |
| Access properties / call it | Yes, unchecked | No, until narrowed |
| Type checking on the value | Disabled | Fully enforced |
| Infectious through expressions | Yes | No |
| Recommended for new code | Avoid | Yes |
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 lettinganyleak 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 thereforenoImplicitAny) so implicitanyis rejected. - Prefer
unknownwhenever you need to accept a value of an undetermined type. - Treat every
anyas technical debt; narrow it to a concrete type as soon as possible. - Confine unavoidable
anyto the smallest scope and validate it at the boundary. - Turn on
@typescript-eslint/no-explicit-anyto flag intentional uses for review. - Remember
anyis infectious — one leak can silently disable downstream checks. - Use targeted type assertions instead of
anyfor one-off conversions.