Skip to content
TypeScript ts functions 4 min read

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 this is fixed by where it is defined, you cannot rebind it with call, apply, or bind, and you cannot give an arrow a this parameter. If a function needs a caller-supplied this, use a function expression instead.

Arrow functions vs. function declarations

AspectArrow functionfunction declaration
this bindingLexical (captured)Dynamic (call-site)
Can declare this parameterNoYes
HoistingNo (const-bound)Yes
arguments objectNo (use rest params)Yes
Usable as constructor (new)NoYes
Can be overloadedNo (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 this is captured lexically.
  • Use a function (not an arrow) when you need a this parameter, overloads, or call-site this binding.
  • Reach for arrow-function class fields to auto-bind handlers, accepting the per-instance cost.
  • Use rest parameters in arrows since they have no arguments object.
  • Add an explicit return type on exported arrows that form a public API.
Last updated June 29, 2026
Was this helpful?