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.
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.
const add = (a: number, b: number): number => {
return a + b;
};
console.log(add(5, 3));
// Output: 8The : 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:
// 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:
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:
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:
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:
type StringTransform = (input: string) => string;
const reverse: StringTransform = (input) => {
return input.split("").reverse().join("");
};
console.log(reverse("TypeScript"));
// Output: tpircSepyTThe 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:
// No contextual type available: parameters need annotations
const logValue = (value: string) => {
console.log(value.toUpperCase());
};
logValue("hello");
// Output: HELLOWithout 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:
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:
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:
// 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:
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:
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:
const square = (n: number): number => n * n;
console.log(square(4));
// Output: 16The 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:
const log = (message: string): void => console.log(message);
log("Done");
// Output: DoneCommon 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:
// 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:
// 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
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.
Frequently Asked Questions
How do I add a return type to an arrow function?
Do arrow functions work differently with this in TypeScript?
Can TypeScript infer arrow function types from context?
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.
More in this topic
TypeScript Compiler Explained
The TypeScript compiler (tsc) turns .ts files into .js files while checking types. Learn what it does, how to use it, and how it fits your workflow.
Set Up ESLint for TypeScript
Install and configure ESLint with typescript-eslint to catch bugs and enforce consistent code style in your TypeScript project.
Pick Utility Type in TypeScript
Pick creates a new type by selecting only the properties you name from an existing type. Use it to build focused types for specific views, API responses, or function parameters.