skip to content

You need to hand an object's method to a callback API such as setTimeout without losing its receiver. Compare binding the method once in the constructor with wrapping the call in an arrow function at the call site.

level: middleimportance: must knowfreq 62%

answer

  1. fix at the definition or at the call site
  2. bind creates an own shadowing property
  3. stable identity versus fresh each time
  4. wrapper just calls it with the dot
  5. some built-ins already take a receiver

basics

~20 s

Binding in the constructor stores an own, permanently-bound copy on the instance, giving one stable function you can pass anywhere. An arrow wrapper at the call site leaves the method untouched and simply calls it with the dot, creating a fresh function each time. Prefer the wrapper unless you need a stable identity.

solid answer

~50 s

Both fixes work, but they put the fix in different places. `this.tick = this.tick.bind(this)` in the constructor adds an **own property** that shadows the prototype method with a permanently bound copy — the receiver travels with the function, every consumer gets it for free, and the identity is stable per instance, which matters when something compares or de-duplicates functions. The cost is one extra function object per instance per method, and the bound copy can no longer be re-targeted. `setTimeout(() => obj.tick(), 100)` instead fixes it at the **call site**: the wrapper does an ordinary method call, so the dot supplies the receiver, the class is unchanged for everyone else, and you can pass arguments explicitly. It allocates a new function each time it is evaluated and looks the property up late. A third option exists where the API supports it: built-ins like `Array.prototype.forEach` take a `thisArg` second argument.

code

javascript · 17 lines
javascript
class Ticker {
  constructor() {
    this.count = 0;
    this.tick = this.tick.bind(this);
  }
  tick() { this.count++; }
}

const t = new Ticker();

console.log(Object.hasOwn(t, 'tick'));                    // true — own property
console.log(t.tick === Object.getPrototypeOf(t).tick);    // false — shadows prototype
console.log(t.tick === t.tick);                           // true — stable identity

const detached = t.tick;
detached();
console.log(t.count);                                     // 1 — receiver travelled

go deeper

for a junior

Know two working fixes and be able to write them: wrap the call in an arrow function, or bind the method once so it carries its object. Say which one you would reach for first and why.

for a middle

Explain the mechanics of each: bind returns a new function stored as an own property that shadows the prototype, while the wrapper simply performs a normal method call. Name the allocation and identity differences between them.

for a senior

Choose deliberately based on who consumes the object — pre-bind when untrusted code holds your methods or a stable reference is needed, wrap locally when you are the only consumer. Be able to justify the per-instance cost you are accepting.

for a principal

Treat it as an interface-design decision: whether your exported surface can be misused at all. Weigh pre-bound or closure-based APIs against prototype sharing and memory, and set one convention rather than leaving each caller to remember.

## The two fixes side by side ```js class Ticker { constructor() { this.count = 0; this.tick = this.tick.bind(this); // fix at the definition } tick() { this.count++; } } const t = new Ticker(); setTimeout(t.tick, 100); // safe: the receiver travels with it ``` ```js class Ticker { count = 0; tick() { this.count++; } } const t = new Ticker(); setTimeout(() => t.tick(), 100); // fix at the call site ``` ## What constructor binding actually creates `Function.prototype.bind` returns a **new** function object whose receiver is fixed. Assigning it to `this.tick` inside the constructor creates an own data property on the instance; the original `tick` is still on the prototype, now shadowed for that instance: ```js Object.hasOwn(t, 'tick'); // true t.tick === Object.getPrototypeOf(t).tick; // false ``` Consequences worth naming in an interview: - **Every consumer is safe.** Callers cannot detach the receiver even by accident, because there is nothing to detach — the function carries it. - **Stable identity per instance.** `t.tick` is the same object every time you read it, so anything that stores functions in a `Set`, keys a `Map` by them, or compares them across renders sees one value rather than a new one each pass. - **Per-instance cost.** One bound function per method per instance, allocated in the constructor. For a handful of long-lived services this is nothing; for thousands of short-lived objects it is measurable. - **No re-targeting.** The bound copy ignores a later attempt to call it with a different receiver, which is usually what you want and occasionally the thing that surprises you. - **Boilerplate.** Each new method needs its own line, and forgetting one reintroduces the bug silently. A class field holding an arrow function (`tick = () => { this.count++; }`, standardised in ES2022) is the modern shorthand for the same shape: also per-instance, also own-property, also stable — with the same allocation cost and the same loss of prototype sharing. ## What the arrow wrapper actually does `() => t.tick()` is not "an arrow that fixes `this`" for the method — the method inside is called with a dot, so the *ordinary* method-call rule supplies the receiver. That distinction matters: - **The class is untouched.** Other call sites, subclasses and tests see the plain prototype method. - **Late lookup.** The property is read when the wrapper runs, so if `t.tick` is replaced (a spy in a test, a decorated version) the wrapper picks up the current one. - **Explicit arguments.** `() => t.tick(payload)` gives you control over what the callback forwards, which is useful when the API passes arguments you do not want. - **A fresh function each evaluation.** Written inside a loop or a frequently-called function, each pass allocates a new wrapper with a distinct identity. If something downstream keys on function identity, that difference is visible. - **The receiver is captured by name.** The wrapper closes over the variable `t`; if that binding is reassigned, the wrapper follows it. ## The third fix: an API that accepts a receiver Some built-ins take a receiver argument, which avoids both an allocation and a wrapper: ```js [1, 2, 3].forEach(counter.add, counter); // thisArg is the second parameter Array.from(iterable, mapper, receiver); // thisArg is the third parameter ``` `Array.prototype.map`, `filter`, `some`, `every`, `find`, `findIndex` and `flatMap` accept the same `thisArg`, as do `Set.prototype.forEach` and `Map.prototype.forEach`. Two caveats: an arrow callback ignores `thisArg` entirely, since an arrow has no own receiver to set; and `setTimeout` does **not** offer one — its extra arguments are forwarded to the callback as parameters. ## How to choose Ask who owns the risk. If your object is handed to code you do not control, or the same handler must be referenced more than once as a single stable value, bind once at construction (or use an arrow class field) so misuse is impossible. If you are the consumer wiring up one call, wrap at the call site — it is local, it leaves the shared prototype method intact, and it reads as exactly what it is. Reach for `thisArg` when the API already provides it. What you should not do is rely on documentation telling every caller to remember `.bind()`.

  • Why does binding in the constructor mean the method is no longer shared via the prototype?
    `bind` returns a new function object, and assigning it to `this.tick` stores it as an own property on the instance. The prototype still holds the original, but property lookup finds the instance copy first. So each instance now owns a distinct function rather than sharing one — that is the allocation cost of the fix.
  • When does the arrow wrapper's fresh-function-per-evaluation behaviour actually cause a problem?
    Whenever something downstream keys on function identity: storing handlers in a `Set` or `Map`, de-duplicating registrations, or memoising on the function reference. Each evaluation of the wrapper expression produces a distinct object, so those comparisons never match. A single bound function held on the instance gives you one stable value instead.
  • Why does passing a thisArg to forEach do nothing when the callback is an arrow function?
    Because `thisArg` works by supplying the receiver for the call, and an arrow function has no own `this` to supply — it resolves `this` from the surrounding scope regardless of how it is invoked. The argument is accepted and simply has no effect, which makes it a quiet source of confusion.

saying these in an interview costs you the question

  • Says bind mutates the original function in place
  • Thinks the arrow wrapper changes the method's own this
  • Believes constructor binding replaces the prototype method for all instances
  • Claims setTimeout accepts a thisArg argument
  • Thinks passing thisArg works with an arrow callback

context