Type Arrow Functions in TypeScript

Arrow functions in TypeScript can be typed just like regular functions. Learn the syntax for typing arrow function parameters, return types, and how contextual typing works differently.

5 min read

To type arrow functions in TypeScript, use the same annotations as a regular function. You annotate the parameters with : type and, when needed, place the return type between the parameter list and the => arrow. The only syntactic difference from a regular function is where the return type goes.

typescripttypescript
const add = (a: number, b: number): number => {
  return a + b;
};
 
console.log(add(5, 3));
// Output: 8

The : number before the arrow is the return type annotation. The parameters are typed the same way as in a regular function.

The Return Type Position

In a regular function, the return type goes after the parameter list and before the opening brace. In an arrow function, it goes between the closing parenthesis and the arrow:

typescripttypescript
// Regular function: return type after params, before {
function multiply(a: number, b: number): number {
  return a * b;
}
 
// Arrow function: return type after params, before =>
const multiplyArrow = (a: number, b: number): number => a * b;

Both produce the same result and the same type checking. The compiler catches return type mismatches in arrow functions just as it does for regular functions:

typescripttypescript
const greet = (name: string): number => {
  return `Hello, ${name}!`;
};

The function body returns a template string, but the annotation promises a number, so the compiler reports this mismatch immediately:

texttext
Type 'string' is not assignable to type 'number'.

The return type annotation caught a mismatch: the function returns a string but the annotation says it should return a number.

Contextual Typing: When You Can Skip Annotations

Arrow functions benefit from contextual typing more often than regular functions do. When an arrow function is passed where a type is already expected, TypeScript infers the parameter types from that context:

typescripttypescript
const names = ["Alice", "Bob", "Charlie"];
 
// No parameter or return type annotations needed
const uppercased = names.map((name) => name.toUpperCase());
 
console.log(uppercased);
// Output: ["ALICE", "BOB", "CHARLIE"]

TypeScript knows the array holds strings, so the map method expects a callback that receives a string and returns a value. The compiler infers the parameter and return types from that expectation. You did not write a single type annotation, but every line is fully type-checked.

Contextual typing also works when assigning an arrow function to a typed variable:

typescripttypescript
type StringTransform = (input: string) => string;
 
const reverse: StringTransform = (input) => {
  return input.split("").reverse().join("");
};
 
console.log(reverse("TypeScript"));
// Output: tpircSepyT

The variable is typed as StringTransform. TypeScript uses that type to infer that the parameter and the return value are both strings. You annotate the variable, not the arrow function itself.

When You Do Need Explicit Annotations

Contextual typing only works when TypeScript already knows the expected type. When the arrow function stands alone with no surrounding type context, you need to annotate the parameters:

typescripttypescript
// No contextual type available: parameters need annotations
const logValue = (value: string) => {
  console.log(value.toUpperCase());
};
 
logValue("hello");
// Output: HELLO

Without the : string annotation, the parameter would be implicitly any in strict mode, and TypeScript would report an error. The rule is the same as for regular functions: parameters need annotations when there is no context for TypeScript to infer from.

The return type annotation is optional when the body makes the return type obvious, but adding it catches mistakes inside the function body:

typescripttypescript
const getLength = (value: string): number => {
  if (value.length > 0) {
    return value.length;
  }
  // Forgot to return in the else branch
};

The if branch returns a number, but there is no return for the case where the string is empty, so the explicit return type catches the gap:

texttext
Function lacks ending return statement and return type does not include 'undefined'.

The explicit : number return type caught a missing return. Without it, TypeScript would infer a wider type that includes undefined, and the bug could go unnoticed at the call site.

Arrow Functions and this

Arrow functions capture this from the enclosing scope. They do not have their own this binding. TypeScript enforces this: you cannot declare a this parameter in an arrow function because there is nothing to type:

typescripttypescript
// Error: An arrow function cannot have a 'this' parameter
const handler = (this: HTMLElement, event: Event) => {
  console.log(event.type);
};

This is different from regular functions, which can declare a this parameter for type checking in callback contexts. If a function needs its own this context, use a regular function declaration or expression instead:

typescripttypescript
class Toggle {
  private active = false;
 
  // Regular function: this refers to the Toggle instance
  handleClick(this: Toggle) {
    this.active = !this.active;
  }
}

Arrow functions inside a class automatically capture the class instance as this, which is often what you want for callbacks:

typescripttypescript
class Timer {
  private seconds = 0;
 
  start() {
    setInterval(() => {
      // Arrow captures this from start(), which is the Timer instance
      this.seconds++;
      console.log(this.seconds);
    }, 1000);
  }
}

For more on this in functions, see classes in TypeScript explained.

Single-Expression Arrow Functions

When an arrow function body is a single expression, you can omit the braces and the return keyword. The return type annotation works the same way:

typescripttypescript
const square = (n: number): number => n * n;
 
console.log(square(4));
// Output: 16

The expression is implicitly returned. The : number return type annotation still applies, and TypeScript checks that the expression type matches.

For a void-returning single-expression arrow, the return type annotation is less common but still valid:

typescripttypescript
const log = (message: string): void => console.log(message);
 
log("Done");
// Output: Done

Common Mistakes

Putting the return type annotation in the wrong place. In an arrow function, the return type goes after the parameter list, before the =>. It does not go after the arrow:

typescripttypescript
// Wrong: return type after the arrow
const bad = (a: number, b: number) => number => a + b;
 
// Correct: return type before the arrow
const good = (a: number, b: number): number => a + b;

Forgetting parentheses around a single typed parameter. When you add a type annotation to a single parameter, you must wrap it in parentheses:

typescripttypescript
// Wrong: single typed parameter without parens
const bad = x: number => x * 2;
 
// Correct: parens required when there is a type annotation
const good = (x: number): number => x * 2;

Adding unnecessary annotations when contextual typing is available. Let TypeScript do the work when the expected type is already clear from the context. Annotate the variable or the callback position, not every arrow function body.

For related topics, see type callback functions in TypeScript and type function parameters in TypeScript.

Rune AI

Rune AI

Key Insights

  • Arrow functions use the same :type syntax for parameters: (x: number) => ...
  • The return type annotation goes between the parens and the arrow: (x: number): number => ...
  • Contextual typing often infers arrow function parameter types, so explicit annotations are optional.
  • Arrow functions capture this from the enclosing scope and cannot have their own this parameter.
  • Arrow functions are expressions, so their type can be inferred from the variable type annotation.
RunePowered by Rune AI

Frequently Asked Questions

How do I add a return type to an arrow function?

Place the return type after the parameter list and before the arrow: (a: number, b: number): number => a + b. The colon and type go between the closing parenthesis and the => arrow.

Do arrow functions work differently with this in TypeScript?

Yes. Arrow functions capture this from their enclosing scope. They do not have their own this binding. TypeScript enforces this: you cannot use a this parameter in an arrow function because there is no this to type.

Can TypeScript infer arrow function types from context?

Yes. When an arrow function is assigned to a typed variable or passed as a typed callback, TypeScript infers the parameter types from the expected type. This is called contextual typing, and it means you often do not need to annotate arrow function parameters.

Conclusion

Arrow functions in TypeScript use the same parameter and return type annotations as regular functions. The return type annotation goes between the parameter list and the arrow: (a: number): number => .... A key difference is contextual typing: when an arrow function is passed where a type is already expected, TypeScript infers the parameter types automatically. Arrow functions also capture this from the enclosing scope, which TypeScript enforces by disallowing this parameters.