cswork / libraryOpen practice workspace

Articles

JavaScript this Interview Questions: Call Sites, Arrow Functions, and Lost Receivers

Practice JavaScript this interview questions on call sites, arrow functions, lost receivers, bind, class callbacks, and listener cleanup.

A method works when you call panel.save(), then throws after you pass panel.save to another function. Nothing happened to the method's source code. The call changed, and the receiver disappeared.

JavaScript this interview questions become easier when you trace three things: the function being called, the receiver supplied by the call, and whether that function creates its own this binding. This article's receiver-trace method is an editorial framework derived from the official language behavior. It gives you a way to explain lost methods, arrow callbacks, constructors, and event handlers without guessing from indentation.

Start with the call, then inspect the function

Official fact: MDN explains that an ordinary function's this generally depends on how it is invoked. Calling object.method() supplies object as the receiver. Calling a strict ordinary function as method() supplies undefined. An arrow function instead uses the surrounding lexical this; it does not create a new binding for a caller to replace.

An object literal does not establish a lexical this scope. Placing an arrow inside { ... } does not make the containing object its receiver. Classes introduce their own rules for field initialization, and constructors introduce another invocation form, so identify those contexts explicitly.

For the following examples, assume an ES module unless stated otherwise. Modules are strict, and their top-level this is undefined. A browser classic script and a Node.js CommonJS module have different top-level behavior. Naming the environment prevents a correct explanation from turning into an incorrect universal output prediction.

Question 1: Why does an extracted method lose its receiver?

const panel = {
  name: "settings",
  identify() { return this.name; },
};

panel.identify();          // "settings"
const identify = panel.identify;
identify();                // TypeError in this module
identify.call(panel);      // "settings"

The assignment copies a function reference into a variable. It does not package the function with panel. At the second call site there is no property access supplying that receiver. The function tries to read name from undefined, which throws.

Now transfer the same function to another object:

const other = { name: "preview", identify: panel.identify };
other.identify(); // "preview"

The original storage location does not permanently own the method's this. Inheritance follows the same principle: an inherited method called through a child object receives the child as its receiver. The object where the property was found can differ from the object used for the call.

Parentheses alone do not detach the receiver: (panel.identify)() still calls through that property reference. Assignment, destructuring into a variable, and the comma expression can discard it. Explain the actual expression instead of relying on “there is a dot somewhere in the code.”

A receiver-trace table for output questions

Use this original trace sheet with the panel example. All entries assume strict ordinary functions unless the row says arrow.

Call expressionRelevant binding ruleResult
panel.identify()Property call supplies panel"settings"
(panel.identify)()Parentheses preserve the property reference"settings"
const f = panel.identify; f()Standalone call has no receiverThrows on this.name
panel.identify.call({name: "dialog"})Explicit receiver"dialog"
panel.identify.bind(panel)()Bound receiver"settings"
const f = () => this; f.call(panel)Arrow uses module-level lexical thisundefined

A useful interview follow-up changes only one column. Replace the ordinary function with an arrow, move the definition into a method, or call the result with new. Recompute the binding rule before predicting output.

Question 2: Why does an arrow callback work inside a method?

const panel = {
  name: "settings",
  makeReader() {
    return () => this.name;
  },
};

const read = panel.makeReader();
read(); // "settings"

The ordinary makeReader method receives panel from its call site. The arrow created during that invocation retains access to that invocation's this. Calling read() later does not create a new receiver binding for the arrow.

Change the outer call and the result changes:

const readOther = panel.makeReader.call({ name: "preview" });
readOther(); // "preview"

The arrow preserves the surrounding invocation's receiver, not an object chosen by visual nesting. If you first extract makeReader and invoke it without a receiver in this module, its this is undefined; the returned arrow will fail when it eventually reads this.name.

This distinction is especially useful with asynchronous callbacks. The delay is not what decides this. The function kind and the invocation contract decide it. An arrow can preserve an already-correct receiver across a callback boundary, but it cannot repair a receiver that was missing before the arrow was created.

Question 3: Why is an arrow object method usually wrong here?

const panel = {
  name: "settings",
  identify: () => this.name,
};

panel.identify(); // TypeError in this ES module

The object literal does not provide a lexical this binding. The arrow reads the module's this, which is undefined. In a classic browser script the same arrow would use the script's global this, potentially producing a different result. Neither case means it reads panel.name.

Use method syntax when the operation should act on whichever object calls it. Use an arrow when the operation should retain an enclosing receiver. These are different contracts, so replacing every function with an arrow is not a general improvement.

Official fact: MDN also documents that arrows cannot be constructors and have no own arguments. Those differences matter if an interviewer asks whether an arrow can replace a regular function wholesale. A shorter syntax does not imply equivalent behavior.

Question 4: When should you bind, wrap, or pass data explicitly?

Suppose a callback API stores a function and later calls it as callback(). Passing panel.save loses the method receiver. Three repairs express different ownership choices.

const fixed = panel.save.bind(panel);
const forwarding = (...args) => panel.save(...args);
const explicit = (...args) => savePanel(panel, ...args);

The bound function holds the original target function and the receiver. If panel.save is replaced later, fixed still invokes the original target. The forwarding wrapper looks up panel.save each time it runs, so it observes method replacement. Both can keep panel reachable while the callback remains registered.

The explicit-parameter version moves object ownership into a normal argument. It can make library contracts easier to test, but it changes the API and does not automatically remove retained references. Choose it when the domain operation is clearer as a function of data rather than an instance method.

RequirementSuitable repairFollow-up to explain
Preserve today's method and receiverBind once and retain the returned functionLater method replacement is not observed
Resolve the current method when calledForward through a stable wrapperProperty lookup occurs on each invocation
Avoid implicit receiver requirementsPass the object or required values explicitlyDefine whether inputs are snapshots or current data
Remove the callback during teardownStore the exact registered functionA second bind() produces a different function

This repair matrix is editorial guidance, not an official preference for one coding style.

Binding fixes can create cleanup bugs

button.addEventListener("click", panel.save.bind(panel));
button.removeEventListener("click", panel.save.bind(panel));

Those two calls create distinct bound functions. The removal does not identify the registered callback. Save one reference and use it for registration and removal, with a matching capture setting.

const onClick = panel.save.bind(panel);
button.addEventListener("click", onClick);
// During teardown:
button.removeEventListener("click", onClick);

This connects receiver correctness with lifetime ownership. Fixing the this error is only half the answer if the panel must later release its listeners. For the broader reachability model, use the JavaScript closure interview guide.

Question 5: What changes in classes and constructors?

Class methods are strict and are not automatically bound. Extracting instance.method can lose the receiver just as it does with an object method. An instance arrow field behaves differently because its initializer runs with the instance as this.

class Counter {
  value = 0;
  increment() { return ++this.value; }
  incrementLater = () => ++this.value;
}

The arrow field remains tied to its instance when extracted. The trade-off is that each instance receives a separate function. A prototype method can be shared across instances; binding that method in every constructor also creates per-instance bound functions. Discuss callback needs and measured costs rather than claiming one form is always faster.

Official constructor rule: when a constructable function is invoked with new, construction supplies a new instance as this. A bound receiver does not replace that instance during construction, although bound arguments still apply. An arrow cannot be made constructable with bind.

Avoid the overbroad slogan “bind always wins.” Say “a bound ordinary call uses its bound receiver,” then handle construction separately. In a derived class constructor, accessing this before super() is another error boundary worth identifying if the prompt includes inheritance.

Question 6: What receiver does a callback API supply?

Do not assume every callback is called as a bare function. Read the API contract. Official Web API behavior: a normal function used with addEventListener receives the listener's current target as this. An arrow listener uses lexical this instead.

Even with a normal listener, event.target and event.currentTarget can differ because an event may originate in a descendant. Prefer the explicit event properties when that distinction is what the code needs. For a bound listener, binding takes precedence over the receiver the event dispatch attempts to supply.

Array methods that accept a thisArg can supply it to an ordinary callback. An arrow ignores that receiver argument for binding purposes. Third-party libraries may choose another contract entirely. “It is a callback” does not answer who supplies this.

Test the contract you just explained

Use a strict module fixture with two objects carrying distinct names. Assert the property call, extracted call failure, explicit call, bound call, lexical arrow, and method-replacement behavior. Distinct values make an accidentally correct receiver visible.

For listener cleanup, test that registration and removal receive the same function reference. For a class callback, test extraction deliberately; a test that only calls instance.method() never exercises the production failure.

Keep the proof boundary clear. A small language fixture verifies the receiver rules in that runtime. Browser event dispatch and library-specific callback contracts need their own integration checks. Do not use a Node.js top-level this result as evidence for a browser module.

Prepare a precise interview answer

Start by naming the environment and function kind. Point to the exact invocation expression, identify its receiver, and explain whether the callable accepts that binding or uses lexical this. Then trace the property access or mutation that succeeds or fails.

When proposing a repair, include the callback's lifetime and identity. A strong answer explains both why the method gets the intended object and how the registration can later be removed.

Sources and Further Reading

Research note: Language rules and Web API contracts come from the linked official documentation. The receiver trace and repair matrix are editorial teaching tools; interview records are qualified separately.