skip to content

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

level: middleimportance: should knowfreq 54%

answer

  1. what happens when inference has nothing
  2. nobody wrote the any
  3. unannotated parameters, untyped imports
  4. callbacks are usually rescued by context
  5. explicit any is still allowed

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.

solid answer

~50 s

An implicit `any` happens when you never wrote `any` but the compiler had nothing to infer from, so it assigns `any` itself. The classic case is a function parameter with no annotation that also has no contextual type: `function double(n) { return n * 2; }`. Other positions are a class member declared with neither a type nor an initialiser, an index access on a type that has no index signature, and importing a JavaScript module that ships no declarations — the whole module becomes `any`. Callbacks are usually rescued by contextual typing: in `items.map(item => ...)` the parameter's type comes from the array, so there is nothing implicit about it. Turning on `noImplicitAny`, which `strict` includes, makes each of these an error. It does not ban writing `any` explicitly — that stays legal and deliberate.

code

typescript · 10 lines
typescript
// With noImplicitAny enabled:
// function double(n) { return n * 2; }  // Error: parameter 'n' implicitly has an 'any' type
function double(n: number) { return n * 2; }

const nums = [1, 2, 3];
const squares = nums.map(n => n * n); // fine: n is contextually typed as number

let pending;          // allowed: an evolving type
pending = "later";    // tracked as string from here
pending.toUpperCase();

go deeper

for a junior

Recall that leaving a function parameter unannotated gives it the any type by default, and that turning on the noImplicitAny option makes the compiler complain about it.

for a middle

Enumerate the fallback positions beyond parameters — uninitialised members, index access without an index signature, untyped imports — and explain why callback parameters usually escape.

for a senior

Show how you would enable the flag on a large existing codebase: expect untyped dependencies to dominate the error count, and decide between declaration shims, typed wrappers, and per-file suppression.

for a principal

Frame it as a policy: what the team gains from banning types nobody chose, what the migration costs, and where explicit escape hatches remain acceptable and reviewable.

## What makes an any implicit TypeScript infers types wherever it can: from initialisers, from return expressions, from the surrounding context. An **implicit any** is what it records when inference has nothing to work with and you did not supply an annotation. No `any` appears in your source, yet the value behaves exactly like one — unchecked property access, unchecked calls, propagation into every derived expression. This is the historically important half of `any`. Explicit `any` is a decision someone made; implicit `any` is a decision nobody made, which is why it tends to sit in a codebase unnoticed for years. ## Where the compiler falls back **Unannotated parameters with no contextual type.** The canonical case: ```ts function double(n) { return n * 2; } // n is implicitly any ``` Nothing constrains `n`, so it is `any`, and so is the return type. Under `noImplicitAny` this is reported as "Parameter 'n' implicitly has an 'any' type". **Class members with neither type nor initialiser.** A property declared as just a name has nothing to infer from and becomes implicitly `any`. **Index access with no index signature.** Reading `obj[key]` where `key` is a general `string` and the object's type declares no index signature has no known result type; the compiler falls back to `any` and, under the flag, reports that the element implicitly has an `any` type. **Untyped imports.** Importing a JavaScript dependency that ships no declaration file and has no types available gives the whole module an implicit `any` — every export from it, and everything derived from those exports, is unchecked. This is often the largest single source of implicit `any` in a real project, and it is invisible in your own source code. ## What contextual typing rescues Most callback parameters are *not* implicit `any`, and candidates who think otherwise end up annotating everything: ```ts const nums = [1, 2, 3]; const squares = nums.map(n => n * n); // n is number, inferred from the array ``` Here the parameter's type flows in from the position the function literal occupies — the signature of `Array.prototype.map` for a `number[]`. The same applies to an arrow passed to a typed callback parameter, or to a function expression assigned to a variable with a function type. Annotating `n: number` there is redundant, and worse, it can silently drift from the real signature. ## The evolving-any exception A bare `let` declaration is tolerated even under the flag: ```ts let pending; // allowed pending = "later"; // the compiler now tracks it as string pending.toUpperCase(); ``` The compiler treats this as an "evolving" type: it watches the assignments that reach each use and gives the variable the type it has actually been given at that point. You only get an error if you try to use the variable while it is still genuinely unknown to the compiler. This exception exists because the pattern is common and safe in practice, and removing it would have made the flag unadoptable. ## What the flag does and does not do `noImplicitAny` converts the fallbacks above into errors. It is off by default and enabled as part of `strict`. Two things it explicitly does **not** do: - It does not forbid explicit `any`. Writing `function f(x: any)` remains legal, because the point of the flag is to eliminate types nobody chose, not to remove the escape hatch. If you also want to discourage explicit `any`, that is a lint-rule concern rather than a compiler one. - It does not change what the emitted JavaScript looks like. Like everything in the type layer, this is pure checking; the output is identical either way. ## Why it matters in an interview The question is really about understanding where inference stops. A candidate who can say "parameters without a contextual type, uninitialised members, unsignatured index access, untyped imports — and callbacks are usually fine because they are contextually typed" has demonstrated that they know how the checker derives types at all, not just that they memorised a flag name. It is also the first flag you turn on when hardening an existing codebase, and knowing that the untyped-import case exists explains why turning it on can produce a wall of errors in files you never edited.

  • Does `noImplicitAny` report an error for a parameter you annotated explicitly as `any`?
    No. The flag targets positions where no annotation exists and inference produced nothing, so an explicit `any` is left alone by design — it is a decision you made and can be reviewed. If a team wants explicit `any` discouraged as well, that belongs to a lint rule, not to the compiler; the compiler's job here is to eliminate types nobody chose.
  • Is `noImplicitAny` on by default?
    No — it is off unless you enable it, and the usual way to get it is by enabling `strict`, which turns it on along with the rest of that family. On an existing codebase, expect the first run to surface errors in files you never touched, particularly around dependencies that ship no type declarations, since an untyped import makes an entire module implicitly `any`.

saying these in an interview costs you the question

  • Thinks noImplicitAny bans writing any explicitly
  • Annotates every callback parameter to satisfy the flag
  • Believes implicit any comes only from function parameters
  • Assumes noImplicitAny is on by default

context