Learn arrow function syntax, implicit returns, and lexical this with clear practical examples.
=> instead of the function keyword.return and braces for one-line expressions with implicit returns.this from the enclosing scope instead of redefining it.map() and filter(), since they keep the code easy to scan.const, since stack traces then show the variable name.this.new throws a TypeError instead of creating an instance.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, silent reassignment.
const sayHi = () => 'Hi';
sayHi = () => 'Yo'; // throws TypeErrorImplicit 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 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);
}
};Arrow inherits thisAn arrow function inherits `this` from its surrounding scope, so it stays bound.
function Timer() {
this.count = 0;
setInterval(() => this.count++, 1000);
}Regular function thisA regular function creates its own `this`, which can cause unexpected values.
function Timer() {
this.count = 0;
setInterval(function () {
this.count++; // this is wrong here
}, 1000);
}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++;
});
}
}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'); // throws: 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; // undefinedUse 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.
Formatting a Product List with Arrow Functions
Chain arrow functions through filter and map to build a display-ready list of in-stock products with formatted prices.
filter() keeps only products where inStock is true, using a one-line arrow function with an implicit return.map() returns a new object literal for each product, so the object needs to be wrapped in parentheses.products array 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' }]Implicit-return arrow functions keep filter/map chains compact — just remember to wrap returned objects in parentheses.