skip to content

Given `const counter = { count: 0, inc: () => { this.count++; } }`, why does calling `counter.inc()` fail to increment `counter.count`, and what do you change?

level: juniorimportance: must knowfreq 80%

answer

  1. arrows carry no this of their own
  2. object literals are not a scope
  3. this fixed where the arrow is written
  4. receiver is computed then discarded
  5. shorthand method restores the binding

basics

~20 s

Arrow functions have no this of their own. The arrow reads this from the scope where the object literal was written, not from the receiver, so counter.inc() touches that outer this instead of counter. Use a normal method.

solid answer

~40 s

An object literal does not create a scope for `this`, and an arrow function never binds its own `this` — it closes over the `this` of the enclosing lexical scope, fixed at definition time. So `inc` sees whatever `this` was where the literal appears: `undefined` at the top level of an ES module or in strict mode, `globalThis` in a sloppy-mode classic script, `module.exports` in a CommonJS file. Calling it as `counter.inc()` sets the receiver to `counter`, but an arrow simply ignores the receiver, so `this.count++` either throws a TypeError on `undefined` or silently creates a global `NaN`. The fix is a function that does take a receiver: shorthand method syntax `inc() { this.count++; }`, or `inc: function () { ... }`. Alternatively drop `this` and close over `counter` by name.

code

javascript · 14 lines
javascript
const outer = {
  name: 'outer',
  make() {
    return {
      name: 'inner',
      viaArrow: () => this.name,
      viaMethod() { return this.name; }
    };
  }
};

const obj = outer.make();
console.log(obj.viaArrow());  // 'outer'  - lexical this from make()
console.log(obj.viaMethod()); // 'inner'  - receiver at call time

go deeper

for a junior

Be able to say plainly that arrow functions have no this of their own and therefore make poor object methods, and to write the shorthand inc() { ... } fix without hesitation.

for a middle

Explain the mechanism: the object literal is not a scope, so this resolves up the scope chain to the enclosing function or top level, and the receiver computed by the method call is simply discarded.

for a senior

Show why the same defect throws in one file and silently corrupts state in another by naming the top-level this of ES modules, CommonJS files, and sloppy-mode scripts, and say how you would spot it in review.

for a principal

Own the guidance: state where arrows are mandated (callbacks capturing surrounding this) versus banned (anything whose contract is to act on its receiver), and how lint rules and strict-mode-by-default modules keep the silent variant from reaching production.

## The rule in one line A regular function gets a fresh `this` on every call, decided by how it was called. An arrow function has no `this` binding at all: the identifier `this` inside an arrow resolves up the scope chain exactly like any other free variable, landing on the `this` of the nearest enclosing ordinary function, class field initializer, method, or — failing all of those — the top-level `this` of the script or module. ## Why the object literal changes nothing The common wrong model is "the arrow is inside the object, so `this` is the object". Object literals are expressions, not scopes. `{ count: 0, inc: () => ... }` introduces no binding for `this`, no variable environment, nothing the scope chain can stop at. The arrow is written in whatever scope the literal itself sits in, and that is where its `this` comes from. ```javascript // top level of an ES module const counter = { count: 0, inc: () => { this.count++; } }; counter.inc(); // TypeError: Cannot read properties of undefined (reading 'count') ``` The error is not about `counter`. It is about the module's top-level `this`, which the spec defines as `undefined`. ## What the outer `this` actually is The failure mode depends on where the literal lives, which is why this bug shows different symptoms in different files: - ES module top level: `this` is `undefined` → TypeError on property access. - Classic script, sloppy mode, top level: `this` is `globalThis` → no error, it quietly increments `globalThis.count`, which starts as `undefined`, so `undefined++` makes it `NaN`. - CommonJS module top level: `this` is `module.exports` → it mutates the exports object. - Inside another ordinary function called plainly under strict mode: `undefined` again. The silent variants are the dangerous ones: no exception, `counter.count` stays `0`, and the state lands somewhere nobody inspects. ## The call form is irrelevant `counter.inc()` is a method call, so the language does compute a receiver — `counter` — and passes it. An arrow function object has no `[[ThisMode]]` that consumes it; the value is discarded. The same is true for `call`, `apply`, `bind`, and the optional `thisArg` of methods like `Array.prototype.map`. Nothing external can redirect an arrow's `this`. That is the point of arrows, and it is exactly wrong for a method, whose whole contract is "operate on whoever called me". ## Fixes Use a function that participates in receiver binding: ```javascript const counter = { count: 0, inc() { this.count++; } // shorthand method — preferred }; counter.inc(); counter.count; // 1 ``` `inc: function () { this.count++; }` behaves identically for this purpose. If you would rather not depend on the receiver at all, close over the object by name: ```javascript const counter = { count: 0 }; counter.inc = () => { counter.count++; }; ``` That version survives extraction — `const f = counter.inc; f();` still works — because it never used `this`. Note the ordering: the arrow can reference `counter` because the reference is resolved when the arrow runs, after the `const` is initialized. ## Where the same rule is a feature The behaviour that breaks methods is what makes arrows the standard callback form. Inside a real method, a nested arrow inherits the method's `this`, so it keeps working where a nested ordinary function would not: ```javascript const counter = { count: 0, incAll(list) { list.forEach(() => { this.count++; }); // `this` is still counter } }; ``` The distinction to carry into an interview: an arrow is right when you want the `this` of the surrounding code, wrong when you want the `this` of the caller. Object methods and prototype methods want the caller's receiver, so they should be ordinary functions. Callbacks handed to other code usually want the surrounding `this`, so they should be arrows. ## Detecting it in review Two grep-able smells: a top-level arrow whose body mentions `this`, and an object literal property written `name: () => ... this ...`. Both are almost always the bug above. Strict mode helps by turning the silent global-mutation variant into a loud TypeError, which is one more reason modules are easier to reason about than classic scripts.

  • The same object literal is loaded once as an ES module and once as a CommonJS file. Does the arrow-method bug behave the same way?
    No. At the top level of an ES module `this` is `undefined`, so `this.count++` throws a TypeError immediately. At the top level of a CommonJS file `this` is `module.exports`, so the same code runs without error and quietly adds a `count` property to the exports object. Same defect, loud in one host and silent in the other.
  • If the arrow is the wrong tool for a method, why is it the right tool for a callback inside a method?
    Because a callback is invoked by someone else — `forEach`, a timer, an event dispatcher — and that caller supplies a receiver you do not want. An arrow ignores it and keeps the enclosing method's `this`, which is exactly the object you are working on. The property that breaks methods is the property that saves callbacks.
  • Does adding the arrow with `counter.inc = () => { this.count++; }` after the literal change anything?
    No. The arrow still captures the `this` of the scope where the arrow expression is written, and an assignment statement creates no new `this`. The only way to make it work is to stop using `this` and reference `counter` by name, or to use an ordinary function so the receiver is honoured.

An arrow function is like a note that says "give it to my manager" — whoever hands it to you, it always points back to the person who wrote it, never to you.

saying these in an interview costs you the question

  • Thinks the object literal supplies this to the arrow
  • Says an arrow's this is decided at call time
  • Claims counter.inc() rebinds this because of the dot
  • Believes bind on the arrow would fix it
  • Assumes the bug always throws, missing the silent global case

context