Arrow Functions and `this` in JavaScript
A practical guide to arrow functions — concise syntax, implicit returns, and the lexical this that makes them perfect for callbacks but wrong for methods and constructors.
What you'll learn
- ✓The full arrow function syntax — and the shortcuts
- ✓When you can drop the braces and the return
- ✓How lexical this differs from dynamic this in regular functions
- ✓Why arrow functions are perfect for callbacks
- ✓Where arrow functions are the wrong tool (methods, constructors)
- ✓Other things arrows do not have — arguments, new, prototype
Prerequisites
- •JavaScript functions — see Functions
Arrow functions are a shorter way to write function expressions. They were added in ES6 and now appear in nearly every modern JavaScript codebase. The syntax savings are real, but the more important difference is how arrow functions handle this. Get that distinction right, and the language gets noticeably smaller.
The syntax
The longest form mirrors a function expression — parameter list, arrow, body in braces:
const add = (a, b) => {
return a + b;
};
console.log(add(3, 4)); // 7
From there, the savings stack up.
Single expression — drop the braces and return
When the body is a single expression, omit the braces and the return. The expression’s value becomes the return value:
const add = (a, b) => a + b;
const square = (n) => n * n;
console.log(add(3, 4)); // 7
console.log(square(5)); // 25
This is the form you’ll see most often.
One parameter — drop the parens
If there’s exactly one parameter, the parentheses around it are optional:
const square = n => n * n;
Many style guides prefer keeping the parens for consistency, especially when types or destructuring may appear later. Pick one rule and stick to it.
Zero or many parameters — parens required
const now = () => Date.now();
const sum = (a, b, c) => a + b + c;
Returning an object — wrap in parens
A bare { after the arrow starts a function body, not an object literal. Wrap an object return in parentheses:
const makePoint = (x, y) => ({ x, y });
console.log(makePoint(1, 2)); // { x: 1, y: 2 }
Without the parens, JavaScript reads { x, y } as a block with two stray expressions and returns undefined. This catches everyone once.
Multi-line bodies use braces
When you need a statement, an early return, or several lines, you’re back to braces and an explicit return:
const classify = (score) => {
if (score < 50) return "fail";
if (score < 70) return "pass";
return "merit";
};
Where arrows shine: callbacks
Arrow functions were designed for the callback patterns you’ve already seen with arrays — map, filter, reduce, forEach, and similar. The terseness lines the operation up with the data:
const nums = [1, 2, 3, 4, 5];
const doubled = nums.map((n) => n * 2);
const evens = nums.filter((n) => n % 2 === 0);
const sum = nums.reduce((acc, n) => acc + n, 0);
console.log(doubled); // [2, 4, 6, 8, 10]
console.log(evens); // [2, 4]
console.log(sum); // 15
Compare with the older form:
const doubled = nums.map(function (n) { return n * 2; });
Same result, more ceremony. The arrow form lets the intent read first.
Try it yourself. Given const words = ["jaguar", "ox", "elephant", "ant", "wolf"], write three arrow expressions: one that produces the lengths of each word, one that keeps only words longer than three letters, and one that produces the total of all word lengths.
this in regular functions
To understand what makes arrows different, recall how this works in a regular function. The value of this is decided at the call site, not where the function was written:
function whoAmI() {
return this;
}
const user = {
name: "Ada",
whoAmI,
};
whoAmI(); // globalThis (or undefined in strict mode)
user.whoAmI(); // the user object
The same function returns different things depending on how it’s invoked. That’s dynamic this. It’s powerful for methods but a constant source of bugs in callbacks.
A classic example — a counter that loses this inside setTimeout:
const counter = {
count: 0,
start() {
setTimeout(function () {
this.count += 1; // 'this' is not counter here
console.log(this.count); // NaN, or TypeError in strict mode
}, 100);
},
};
counter.start();
The function passed to setTimeout is called as a plain function, so its this is not counter. People used to work around this with var self = this; or .bind(this). Arrow functions made that workaround obsolete.
this in arrow functions — lexical
Arrow functions do not have their own this. They inherit this from the surrounding scope where they were defined — lexically, in language jargon.
The counter above just works if the callback is an arrow:
const counter = {
count: 0,
start() {
setTimeout(() => {
this.count += 1; // 'this' is counter — inherited from start
console.log(this.count); // 1
}, 100);
},
};
counter.start();
start is a regular method, so its this is counter. The arrow inside it captures that same this. This is the single most useful property of arrow functions, and the reason they took over callback-style code.
The same idea applies inside iteration:
const group = {
name: "Admins",
members: ["Ada", "Linus", "Grace"],
greetAll() {
this.members.forEach((m) => {
console.log(`${m} belongs to ${this.name}`);
});
},
};
group.greetAll();
// Ada belongs to Admins
// Linus belongs to Admins
// Grace belongs to Admins
The arrow’s this is the surrounding greetAll’s this, which is group. No tricks required.
Where arrows are the wrong tool
Lexical this is wonderful for callbacks, and exactly wrong in a few places.
Object methods
If you define a method as an arrow at the top level of an object literal, its this is the outer scope — usually the module or window — not the object:
const counter = {
count: 0,
increment: () => {
this.count += 1; // 'this' is NOT counter
},
};
counter.increment();
console.log(counter.count); // 0 — nothing happened
For methods, use the regular method shorthand:
const counter = {
count: 0,
increment() {
this.count += 1;
},
};
Constructors
Arrow functions cannot be called with new. They have no prototype and no internal construct mechanism:
const Point = (x, y) => {
this.x = x;
this.y = y;
};
new Point(1, 2); // TypeError: Point is not a constructor
Use a regular function or, better, a class.
Methods on a prototype or class
Same reason as object methods — prototype/class methods rely on dynamic this to work for any instance. Class method syntax is already concise; there’s no benefit to using an arrow.
class Counter {
count = 0;
increment() { // correct
this.count += 1;
}
}
Anywhere you really need arguments
Arrow functions don’t have their own arguments object. Use rest parameters instead:
const sum = (...nums) => nums.reduce((a, b) => a + b, 0);
This is rarely a real problem — rest is better than arguments anyway.
A quick reference table
| Feature | Regular function | Arrow function |
|---|---|---|
Own this | Yes (dynamic) | No (lexical) |
arguments object | Yes | No (use rest) |
Callable with new | Yes | No |
prototype property | Yes | No |
| Method shorthand in objects | Use this | Avoid |
Short callback in .map/.filter | Works | Best fit |
Use this as a checklist when you can’t decide which form to reach for.
Try it yourself. Predict the output of this code and then run it.
const obj = {
name: "lexical",
regular: function () { return this.name; },
arrow: () => this.name,
method() { return this.name; },
};
console.log(obj.regular());
console.log(obj.arrow());
console.log(obj.method());Explain to yourself why each line produces what it does.
A small worked example
A countdown timer that uses an arrow inside a method, demonstrating lexical this in the place arrows were made for.
class Countdown {
constructor(from, label) {
this.value = from;
this.label = label;
}
start() {
const tick = () => {
console.log(`${this.label}: ${this.value}`);
this.value -= 1;
if (this.value < 0) {
clearInterval(this.handle);
console.log(`${this.label} done.`);
}
};
this.handle = setInterval(tick, 500);
}
}
new Countdown(3, "Launch").start();
// Launch: 3
// Launch: 2
// Launch: 1
// Launch: 0
// Launch done.
tick is an arrow, so this inside it is the instance. If tick were a regular function, this would be undefined (in strict mode) when setInterval called it, and the whole thing would crash.
Recap
You now know:
- Arrow functions are a concise function expression:
(a, b) => a + b - Single-expression bodies have an implicit return; multi-line bodies use braces
- Returning an object literal needs parentheses:
() => ({ x: 1 }) - Arrows have lexical
this— they inherit it from the surrounding scope - Arrows are perfect for callbacks:
setTimeout,map,filter, event handlers - Arrows are wrong for object methods, class methods, and constructors
- Arrows have no
argumentsobject — use rest parameters instead
Next steps
The next post covers two patterns you’ve already glimpsed and that pair beautifully with functions — destructuring inputs and using spread/rest in calls and array literals.
→ Next: Destructuring, Spread, and Rest in JavaScript
Questions or feedback? Email codeloomdevv@gmail.com.