JavaScript Arrow Functions
Learn arrow function syntax, implicit returns, and lexical this with clear practical examples.
TL;DR
- 01Write concise callbacks using
=>instead of thefunctionkeyword. - 02Skip
returnand braces for one-line expressions with implicit returns. - 03Inherit
thisfrom the enclosing scope instead of redefining it.
Tips
- 01Use arrow functions for short callbacks inside
map()andfilter(), since they keep the code easy to scan. - 02Name longer arrow functions by assigning them to a
const, since stack traces then show the variable name.
Warnings
- 01Do not use arrow functions as methods on objects when those methods need access to the object through
this. - 02Arrow functions cannot be used as constructors, so calling one with
newthrows a TypeError instead of creating an instance.
Basic Syntax
One parameterDrop the parentheses when the function takes exactly one parameter.
const double = n => n * 2;
double(4); // 8Multiple parametersWrap parameters in parentheses when the function takes two or more.
const multiply = (a, b) => a * b;
multiply(2, 5); // 10No parametersUse empty parentheses when the function takes no parameters at all.
const greet = () => 'Hello!';
greet(); // "Hello!"Anonymous by defaultArrow functions are anonymous and are usually assigned to a variable.
const handlers = [];
handlers.push(() => console.log('clicked'));Use constAssign arrow functions with `const` to prevent accidental reassignment.
const sayHi = () => 'Hi';
sayHi = () => 'Yo'; // TypeError: read-onlyReturning Values
Implicit returnDrop the braces and the return keyword to return a single expression.
const sum = (a, b) => a + b;Block bodyUse curly braces with return for multi-line function bodies and logic.
const check = num => {
if (num > 10) return 'big';
return 'small';
};Object literalWrap returned object literals in parentheses to avoid a syntax error.
const make = id => ({ id, active: true });Array methodsImplicit returns work well inside array methods like `filter()` and `map()`.
const evens = [1, 2, 3, 4]
.filter(n => n % 2 === 0)
.map(n => n * 10); // [20, 40]Pick a styleMatch the return style to the code's complexity and readability needs.
const isEven = n => n % 2 === 0; // implicit
const classify = n => { // block
if (n < 0) return 'negative';
return n % 2 === 0 ? 'even' : 'odd';
};Promise and Async Patterns
Promise chainsMost real-world arrow functions live inside `.then()`, `.catch()`, or async wrappers.
fetch('/api/user')
.then(res => res.json())
.then(data => console.log(data))
.catch(err => console.error(err));Promise.all()Run independent async calls together and destructure results once every promise resolves.
const loadDashboard = async () => {
const [users, posts] = await Promise.all([
fetch('/api/users').then(r => r.json()),
fetch('/api/posts').then(r => r.json()),
]);
};Async arrowUse an arrow function as the body of an async function for compact definitions.
const getUser = async (id) => {
const res = await fetch(`/users/${id}`);
return res.json();
};Async IIFERun an async arrow immediately when you need `await` outside any named function.
(async () => {
const data = await fetchDashboardStats();
console.log(data);
})();try/catchWrap `await` calls in `try/catch` to handle rejected promises inside async arrows.
const loadUser = async (id) => {
try {
const res = await fetch(`/users/${id}`);
return await res.json();
} catch (err) {
console.error('Failed to load user', err);
}
};Lexical This
Arrow inherits thisAn arrow function inherits `this` from its surrounding scope, so it stays bound.
function Timer() {
this.count = 0;
setInterval(() => this.count++, 1000); // works
}Regular function thisA regular function creates its own `this`, which can cause unexpected values.
function Timer() {
this.count = 0;
setInterval(function() { this.count++; }, 1000); // breaks
}Class methodsThis behavior makes callbacks inside class methods work without manual binding.
class Counter {
count = 0;
increment = () => { this.count++; };
}Method callbacksArrow callbacks inside a method inherit `this` from that method, not the caller.
const cart = {
items: [{ price: 10 }, { price: 25 }],
total() {
return this.items.reduce(
(sum, i) => sum + i.price, 0
);
},
};
cart.total(); // 35Event listenersBind event handlers with arrow functions so `this` still points at the class instance.
class Button {
clicks = 0;
constructor(el) {
el.addEventListener('click', () => {
this.clicks++;
});
}
}Limitations
No arguments objectArrow functions have no `arguments` object; use rest parameters to collect arguments instead.
function legacyLogger() {
return arguments.length;
}
const modernLogger = (...args) => args.length;No constructorsArrow functions cannot be used as constructors with the `new` keyword.
const Person = (name) => { this.name = name; };
new Person('Alex'); // TypeError: not a constructorNo generatorsArrow functions cannot be used as generator functions with `yield`.
function* range(n) {
for (let i = 0; i < n; i++) yield i;
}
[...range(3)]; // [0, 1, 2]No prototypeArrow functions have no `prototype` property, since they can never act as constructors.
const Greeter = () => {};
Greeter.prototype; // undefinedIn Practice
Chain arrow functions through filter and map to build a display-ready list of in-stock products with formatted prices.
- 01
filter()keeps only products whereinStockis true, using a one-line arrow function with an implicit return. - 02
map()returns a new object literal for each product, so the object needs to be wrapped in parentheses. - 03A template literal inside the arrow function formats each price to two decimal places.
- 04The result is a fresh array — the original
productsarray is never mutated.
const products = [
{ name: 'Desk Lamp', price: 24.5, inStock: true },
{ name: 'Notebook', price: 4.99, inStock: true },
{ name: 'Stapler', price: 12, inStock: false },
];
const display = products
.filter(p => p.inStock)
.map(p => ({ label: `${p.name} — $${p.price.toFixed(2)}` }));
console.log(display);
// [{ label: 'Desk Lamp — $24.50' }, { label: 'Notebook — $4.99' }]FAQ
Use arrow functions for short, inline callbacks like those passed to map(), filter(), or setTimeout(). Stick with regular functions when you need your own this binding, the arguments object, or when defining object methods.
Wrap the object literal in parentheses: const getUser = () => ({ name: 'Alice', age: 30 }). Without the parentheses, the curly braces are parsed as a function body instead of an object.
Arrow functions capture this from the enclosing lexical scope at definition time, not the call site. If you need this to refer to a specific object at runtime, use a regular function instead.
No, arrow functions cannot act as constructors and throw a TypeError when called with new. They also lack a prototype property, so use a regular function or a class to instantiate objects.
The concise body form () => value implicitly returns the expression with no braces or return keyword. The block body form () => { return value; } uses an explicit return. Use the block form for multiple statements.