skip to content

With TypeScript's noImplicitAny flag enabled, why does `function greet(name) {}` fail to compile while the `u` in `users.map(u => u.id)` needs no annotation at all?

level: juniorimportance: must knowfreq 70%

answer

  1. implicit, not explicit, is the target
  2. inference first, any only as fallback
  3. the callback's type comes from the target signature
  4. error 7006 on parameters
  5. untyped imports are caught too

basics

~20 s

noImplicitAny only errors where the compiler has no type to infer. A standalone parameter has no source of type information, so it would silently become any; a callback parameter is contextually typed from the signature it is passed to, so its type is inferred rather than implicit.

solid answer

~40 s

`noImplicitAny` does not ban `any` — it bans an `any` that nobody wrote and nobody noticed. When a declaration has no annotation, the compiler tries to infer a type; only if that fails does the declaration fall back to `any`, and this flag turns that fallback into error 7006, "Parameter 'name' implicitly has an 'any' type." A standalone function declaration gives the checker nothing to work from, so `greet`'s parameter errors. A callback passed to `Array.prototype.map` is different: the target signature says the callback's first parameter is the array's element type, so `u` is *contextually typed* and comes out as the element type. Writing `any` explicitly still compiles — the flag is about the invisible case. It also catches imports of untyped JavaScript modules, where the whole import would otherwise be `any`.

code

typescript · 9 lines
typescript
const users = [{ id: 1 }, { id: 2 }];

// Contextual typing: map's signature supplies the type of u.
const ids = users.map(u => u.id);

// No context here, so the parameter must be annotated.
function greet(name: string): string {
  return `Hello, ${name}`;
}

go deeper

for a junior

Know that the error means the compiler found no type for that declaration, and that the fix is to annotate it with the real type rather than with any. Be able to say why callbacks usually need no annotation.

for a middle

Explain the mechanism: inference from an initializer or return expression, contextual typing flowing inward from an expected signature, and the any fallback that the flag turns into an error. Mention the untyped-import case.

for a senior

Show how implicit any spreads through a call chain and hides bugs, and describe how you would eliminate the untyped-dependency variant — community types or a hand-written declaration file scoped to the surface you actually use.

for a principal

Take a position on where annotations are mandatory versus where inference is trusted, and on whether explicit any needs its own gate beyond the compiler. The point is a consistent boundary contract, not a per-file taste argument.

## The rule in one line `noImplicitAny`, one of the flags `strict` turns on, makes the compiler report an error wherever a declaration would otherwise receive the `any` type without anyone having asked for it. The key word is *implicitly*. `any` is still a legal type you can write, and this flag says nothing about it: ```ts function parse(input: any) { /* compiles fine under noImplicitAny */ } ``` What the flag objects to is an `any` that appears by omission, because such a type is invisible in review and disables checking for everything the value touches. ## Where a type can come from When you leave an annotation off, the checker looks for a source of type information: - **An initializer.** `const count = 0` infers `number`; no annotation needed, no error. - **A return expression.** A function's return type is inferred from its `return` statements, so an unannotated return type is never an implicit-any error. - **A contextual type.** When an expression appears in a position whose expected type is already known, that expectation flows *inward* into the expression. This is what rescues callbacks. A plain function declaration's parameter has none of these. There is no expression to infer from and no surrounding expectation, so the parameter would be `any` and the flag errors: `Parameter 'name' implicitly has an 'any' type. (7006)`. ## Contextual typing, concretely `Array.prototype.map` is declared in the standard library roughly as `map<U>(callbackfn: (value: T, index: number, array: T[]) => U): U[]`. When you write `users.map(u => u.id)`, the compiler knows the argument must match `(value: T, index: number, array: T[]) => U` with `T` fixed by the array. That expectation supplies the type of `u`, so `u` is inferred as the element type rather than falling back to `any`. The same mechanism types the parameters of a function expression assigned to an annotated variable, or an object literal method matching an interface member. The practical rule this produces: annotate at the places where types *enter* your program — function declarations, exported APIs, module boundaries — and let inference carry them everywhere else. ## What the flag does not flag - **An explicit `any`.** As above, that is a decision, not an accident. - **`let x;` followed by assignments.** The compiler tracks an "evolving any" here: it watches the assignments and gives `x` a useful type at each use. You only get an error if it genuinely cannot determine a type at a point where the value is read. - **A missing return annotation.** Inferred from the body. ## The one that surprises people: untyped imports If you import a JavaScript package that ships no type declarations, the entire imported value would be `any`. `noImplicitAny` reports it: `Could not find a declaration file for module 'x'. ... implicitly has an 'any' type. (7016)`. This is the flag doing its most valuable work, because an untyped import poisons everything downstream of it. The real fixes are to install the package's community types if they exist, or to write a small declaration file of your own for the surface you actually use. Silencing it with a blanket `declare module 'x';` is possible but simply reinstates the `any`. ## Why juniors should care `any` is contagious. Any expression derived from an `any` is itself `any`, so one unannotated parameter can switch off checking across a whole call chain, and the failure mode is not a loud error but a quiet absence of errors. `noImplicitAny` is the flag that makes those holes visible, which is why it is switched on by `strict` and why interviewers open with it. If you are asked to fix the error, the answer is almost always to annotate the parameter — reaching for `: any` to make the message go away hands back exactly what the flag was protecting.

  • Does noImplicitAny stop you from writing the any type?
    No. It only reports declarations that would become `any` because nothing supplied a type. An explicit `: any` still compiles, which is deliberate: the flag's goal is to make the choice visible in the source and in review, not to remove the escape hatch. If you want to restrict explicit `any` as well, that is a lint concern, not a compiler one.
  • Why is an unannotated return type not an implicit-any error?
    Because the compiler infers a return type from the function's `return` statements, so there is no fallback to `any`. Annotating a return type is still often worth it on exported functions: it pins the contract, produces a clearer error at the definition rather than at every call site, and stops an accidental widening from silently changing your public API.

saying these in an interview costs you the question

  • Thinks noImplicitAny forbids writing any explicitly
  • Says every unannotated parameter is an error, callbacks included
  • Believes an unannotated return type triggers the error
  • Fixes the error by annotating the parameter as any
  • Cannot explain where a callback parameter's type comes from

context