"You don't have to tell TypeScript what parameters mean when the call site already does."
What contextual typing is
When a function is in a position where the surrounding code already declares the function type — e.g., the callback parameter of arr.map(...) is known to be (x: T, i: number) => U — TypeScript uses that declaration to type the function's parameters automatically. You don't have to annotate (x); TypeScript already knows what x is from the context.
This is the reason you can write arr.map(x => x.length) without ever specifying that x is a string. The compiler reads arr's type (say string[]), looks up .map's signature, sees that the callback takes a string, and types x accordingly. The arrow function inherits its parameter types from the slot it's filling.
Where contextual typing happens
- Function arguments:
arr.filter(x => x > 0)—xtyped from the array element type. - Object property assignments to typed slots:
const cb: (x: number) => void = (x) => ...—xisnumber. - Right side of typed variable declarations:
const f: Reducer<User> = (acc, u) => ...— both params typed from the alias. - Return-position function shapes: when a function returns another function and the outer return type is declared, the inner function's params are typed.
Why it matters
Contextual typing is what makes TypeScript's callback ergonomics feel natural. Without it, you'd have to annotate every callback parameter — arr.map((x: string) => x.toUpperCase()) — which is repetitive and fragile (rename the array's element type and you'd have to update every callback). With it, callbacks adopt their parameter types from the API they're passed to. The result is concise, correct, and refactor-friendly.
When contextual typing fails
Contextual typing requires the surrounding context to already declare the function type. A standalone arrow function in a let binding without an annotation can't infer parameter types — there's nothing for them to be inferred from. The fix is to annotate the variable (or use it in a typed position).
const f = (x) => x + 1; // ❌ Parameter 'x' implicitly has 'any' type
const g: (x: number) => number = (x) => x + 1; // ✅ contextual from the variable's type
[1, 2].map((x) => x + 1); // ✅ contextual from the array
Pippa's confession
.then — and types every callback parameter automatically. The result is readable code that the type system still understands. Every time a beginner annotates a callback parameter that didn't need it, I gently delete it. Inference does this better than you do.