Arrow Functions
Arrow functions are the concise, lexically-scoped function form introduced in ES2015 and used throughout modern TypeScript. They differ from function declarations in two ways that matter for typing: they capture this from the surrounding scope rather than binding their own, and they are frequently the target of contextual typing, where TypeScript infers their parameter types from the surrounding context. This page covers annotating arrow functions, how contextual typing removes the need for redundant annotations, the lexical this behavior, and where arrow functions help or hinder.
Annotating an arrow function
You can annotate an arrow function’s parameters and return type just like a regular function. A single-expression body has an implicit return, so its return type is inferred from the expression.
const double = (n: number): number => n * 2;
const greet = (name: string) => `Hello, ${name}`; // return inferred as string
const add = (a: number, b: number) => a + b;
When the body is a block ({ ... }), you use an explicit return statement, and the return type is inferred from it unless annotated.
Contextual typing removes redundant annotations
When an arrow function is written where TypeScript already knows the expected type — a callback argument, or a value assigned to a typed variable — the parameter types are inferred automatically. This is contextual typing.
type Comparator = (a: number, b: number) => number;
const byValue: Comparator = (a, b) => a - b; // a, b inferred as number
[3, 1, 2].sort((a, b) => a - b); // a, b inferred from sort's signature
["x", "y"].map((item) => item.toUpperCase()); // item inferred as string
Because the target type supplies the parameter types, annotating them again would be redundant. Annotate only when there is no context to infer from (for example, a standalone arrow assigned to an untyped const).
Lexical this
An arrow function does not create its own this; it captures this from the enclosing lexical scope. This is the key behavioral difference from function and the reason arrows are ideal for callbacks inside class methods.
class Counter {
count = 0;
start() {
// Arrow captures the Counter instance's `this`
setInterval(() => {
this.count++;
}, 1000);
}
}
A regular function () { this.count++ } passed to setInterval would have its own this (or undefined in strict mode), breaking the reference. The arrow’s lexical capture keeps this pointing at the instance.
Because an arrow’s
thisis fixed by where it is defined, you cannot rebind it withcall,apply, orbind, and you cannot give an arrow athisparameter. If a function needs a caller-suppliedthis, use afunctionexpression instead.
Arrow functions vs. function declarations
| Aspect | Arrow function | function declaration |
|---|---|---|
this binding | Lexical (captured) | Dynamic (call-site) |
Can declare this parameter | No | Yes |
| Hoisting | No (const-bound) | Yes |
arguments object | No (use rest params) | Yes |
Usable as constructor (new) | No | Yes |
| Can be overloaded | No (use a typed variable) | Yes |
For methods that may be called with different receivers, or for overloaded functions, prefer a function. For callbacks and short transforms, prefer arrows.
Class fields as arrow functions
Defining a class method as an arrow-function field auto-binds this, which is useful when passing the method as a callback (for example, a React event handler).
class Button {
label = "Click";
// `this` is permanently bound to the instance
handleClick = (): void => {
console.log(this.label);
};
}
const onClick = new Button().handleClick;
onClick(); // ✅ logs "Click" — `this` stays bound
The trade-off is that each instance gets its own copy of the function (it lives on the instance, not the prototype), so use this pattern deliberately rather than by default.
Best Practices
- Use arrow functions for callbacks and short transforms; rely on contextual typing instead of re-annotating parameters.
- Annotate parameters only when there is no context to infer them from.
- Prefer arrows inside methods that pass callbacks, so
thisis captured lexically. - Use a
function(not an arrow) when you need athisparameter, overloads, or call-sitethisbinding. - Reach for arrow-function class fields to auto-bind handlers, accepting the per-instance cost.
- Use rest parameters in arrows since they have no
argumentsobject. - Add an explicit return type on exported arrows that form a public API.