How is `this` determined in JavaScript, and why does a method lose it when you pass it as a callback?
`this` is decided by how a function is called, not where it is written: a method call binds the receiver, `new` binds the fresh object, `call`/`apply`/`bind` bind explicitly, and a plain call gives `undefined` in strict mode or the global object in sloppy mode. Extracting a method into a callback drops the receiver.
What the interviewer is scoring
- Does the candidate locate the decision at the call site rather than at the definition
- That they can state all four call forms and which one wins when two apply
- Whether the strict-mode versus sloppy-mode difference for a plain call is known precisely, including that class bodies and modules are always strict
- Does the candidate describe arrow functions as having no `this` of their own, rather than as "auto-binding" it
- Whether a fix is chosen with a reason attached, instead of reaching for `bind` reflexively
Answer
this is an argument, not a property
The model that makes every case fall out is this: this is an implicit parameter passed at the moment of the call. It is not stored on the function, it is not captured when the function is written, and functions do not belong to the objects you find them on. Two calls to the same function object can receive two different values, because the value comes from the call expression.
There are four ways a call supplies it, and when more than one could apply they resolve in a fixed order.
new Fn()— the highest precedence. A fresh object is created withFn.prototypeas its prototype and passed asthis. If the constructor returns no object, that fresh object is the result.fn.call(obj),fn.apply(obj),fn.bind(obj)— explicit binding.binddoes not call the function; it returns a new one whose receiver is permanently fixed, which is why re-binding a bound function cannot change it.obj.method()— implicit binding. The receiver is whatever is to the left of the final dot at the call site, evaluated fresh each time.fn()— the default. In strict modethisisundefined; in sloppy mode it is substituted with the global object (globalThis, which iswindowin a browser).
new beating explicit binding is the one ordering people get wrong. Point.bind({ x: 'ignored' }) produces a constructor whose bound receiver is discarded the moment you call it with new, because the [[Construct]] path allocates its own object.
Why extraction breaks a method
Implicit binding is a property of the call expression, so it does not survive being taken apart. The instant you write const f = obj.method, you have a plain function reference and nothing recording where it came from. Calling f() is a plain call, so the default rule applies.
class Counter {
count = 0;
increment() {
this.count += 1; // `this` is whatever the call site supplied
return this.count;
}
}
const c = new Counter();
console.log(c.increment()); // logs 1 - the receiver is c
console.log(c.increment()); // logs 2
const inc = c.increment;
inc(); // TypeError: Cannot read properties of undefined (reading 'count')
document.body.addEventListener('click', c.increment);
// Same defect: the DOM calls the function with the element as receiver,
// so `this.count` reads a property the element does not have -> NaN, silently.
Class bodies are always strict mode regardless of the surrounding file, and ES modules are too. That is why the extracted call throws rather than quietly writing count onto the global object, which is what the equivalent code in a sloppy-mode <script> would do. Sloppy mode turns this defect from a stack trace into a mutation of globalThis surfacing as wrong numbers elsewhere, which is the concrete reason strict mode is the better default and not merely the modern one.
No intermediate variable is needed for this. (0, obj.method)() also discards the receiver, because only a call whose callee is a member expression performs implicit binding.
Arrow functions capture instead
An arrow function has no this binding of its own. References to this inside it resolve like any other free variable, up the lexical scope chain to the nearest enclosing ordinary function, method, or module or script top level. That is a scoping rule rather than a binding rule, so call, apply and bind have no effect on it: they still run the function, but the thisArg is inert.
const timer = {
ticks: 0,
start() {
// Captures start()'s `this`, which is `timer`, at every tick.
setInterval(() => { this.ticks += 1; }, 1000);
// A plain function here would instead receive whatever the timer API
// hands it - `window` in a browser, the Timeout object in Node - never
// `timer`, and strict mode does not change that because a receiver
// was supplied.
},
};
The distinction is not pedantry. Describing arrows as "binding this to the instance" predicts the wrong behaviour twice: it implies an arrow written as an object literal's property would see that object, when it sees whatever this was in the enclosing scope, and it implies such an arrow can be rebound. Only "no own this, resolved lexically" answers both correctly.
Choosing a fix
For a class method that will be passed around, define it as a class field holding an arrow function. It is created per instance during construction, when this is the instance, so the captured receiver is correct and no call site can undo it. The cost is one function object per instance instead of one shared on the prototype, and because it no longer lives on the prototype it cannot be overridden by a subclass in the usual way or stubbed via the prototype in tests.
bind in the constructor gives the same guarantee and keeps the prototype method intact, which is what you want when subclasses or spies need it. Wrapping at the call site, as in onClick={() => c.increment()}, leaves the method untouched and suits the case where you also pass arguments, at the price of a new closure per render. Some APIs take a receiver directly, such as the second argument to Array.prototype.map, and using it beats wrapping.
Diagnose before you fix. If
thisis wrong, identify which of the four call forms is being taken and what the call site actually supplies; reaching forbindwithout that step often just moves the problem to a different receiver.
Likely follow-ups
- What does `this` refer to inside an arrow function written at the top level of an ES module?
- Why does `new` on a bound function ignore the bound receiver?
- How would you write a decorator or wrapper that forwards `this` correctly to the function it wraps?
- Why can an arrow function not be used as a constructor or a generator?
Related questions
- What is a closure, and how does one end up leaking memory?mediumAlso on javascript4 min
- Why does my useEffect run twice on mount?easyAlso on strict-mode5 min
- Adding a second LEFT JOIN doubled the revenue figure on a report. Explain what happened and write the correct query.mediumSame kind of round: coding4 min
- Your test still reaches the network even though you patched the client. Talk me through fixture scope, parametrisation, and where a patch has to point.mediumSame kind of round: concept4 min
- This form marks invalid fields with red text and a red border. What has to change?mediumSame kind of round: concept4 min
- How do you remove elements from a collection while iterating it, and what makes an iterator fail-fast?mediumSame kind of round: concept5 min
- How would you rewrite a row-by-row pandas transformation, and what is SettingWithCopyWarning telling you?mediumSame kind of round: coding5 min
- Write a query returning the second-highest salary in each department, then tell me what it does when two people tie for the top.mediumSame kind of round: coding3 min