In JavaScript, an arrow function expression is a compact alternative to a traditional function expression. I would argue that most people use them for their brevity, which is absolutely fine. But they differ from traditional function expressions in a few important ways, most notably in how they handle the this keyword.

In the following code snippet, the timer constant is assigned to an object literal that represents a countdown timer. The object literal has two properties: ms with a value of 5000, and interval with a value of 1000. The ms property represents the total number of milliseconds for the countdown, while the interval property represents the number of milliseconds between each tick. The object literal also has a tick() method that starts the countdown. But it’s broken! Can you figure out why?

const timer = {
ms: 5000,
interval: 1000,
tick() {
console.log(this.ms);

const interval = setInterval(function () {
this.ms -= this.interval;
console.log(this.ms);
if (this.ms === 0) clearInterval(interval);
}, this.interval);

return interval;
},
};

timer.tick(); // 5000, NaN, ..., NaN

The problem is that the the setInterval() method invokes its callback function in a separate execution context from the tick() method. This means the value of the callback function’s this keyword is not the object referenced by the timer constant. In the browser, it’s the global object, i.e. the window object. In Node.js, it’s the instance of the Timeout class created internally by the setInterval() method.

When the tick() method is invoked, it logs the initial value of the ms property, which is 5000. Because the callback function for the setInterval() method is invoked in a separate execution context, it tries to access the ms and interval properties on the window object (in the browser) or the Timeout instance (in Node.js). Both of these have the value undefined, and undefined - undefined evaluates to NaN (not a number). The condition of the if statement is never truthy (because undefined is never strictly equal to 0) and so the interval keeps running indefinitely.

You can fix this problem by replacing the traditional function expression with an arrow function expression. This works because arrow function expressions do not have their own binding to the this keyword. They always use the value from their lexical scope, which is basically the scope in which they’re defined. In this example, the arrow function expression is defined in the lexical scope of the tick() method, where the value of the this keyword is the object referenced by the timer constant.

const timer = {
ms: 5000,
interval: 1000,
tick() {
console.log(this.ms);

const interval = setInterval(() => {
this.ms -= this.interval;
console.log(this.ms);
if (this.ms === 0) clearInterval(interval);
}, this.interval);

return interval;
},
};

timer.tick(); // 5000, 4000, 3000, 2000, 1000, 0

So, aside from brevity, when should you use an arrow function expression in JavaScript? When you need the value of a function’s this keyword to be the same one from its surrounding (lexical) scope.

For more on the this keyword, check out You Don’t Know JS by Kyle Simpson.