Writing · MARGIN NOTESFront-End Development
4 Rules That Decide What this Means in JavaScript (call, apply, bind)
The JavaScript keyword this is not "the object the function lives on." The call site decides. Here is the interview mental model for method calls, extraction traps, call/apply/bind, new, arrows, and strict mode.
- Author
- Nicola Riker Urdaneta
- Published
- October 13, 2026
- Reading time
- 7 min read

Contents
In interviews, "explain this" is rarely a request for a glossary line. Interviewers want to know whether you can predict the receiver for a given call, recover it when a method is passed as a callback, and explain why an arrow behaves differently from a method.
this is set by how a function is invoked (and, for arrows, by where it was defined). Once you own that call-site model, call, apply, bind, and class syntax stop feeling like separate topics.
Why interviewers ask
The prompt usually looks like one of these:
- What is
thisinside this method? What if we extract the method and call it alone? - Fix a bug where a click handler or
setTimeoutloses the object. - What do call, apply, and bind do, and when would you use each?
- Why does an arrow "not work" as an object method, or why does it fix a nested callback?
They are testing five things at once:
- Do you know call-site rules (method call, bare call, new, explicit bind) instead of "whatever object the function was written on"?
- Can you spot the method-extraction trap and name a fix (bind, or a wrapper arrow
() => obj.fn()that keeps the method call)? - Do you know arrows close over lexical
thisand do not get their ownthis? - Can you contrast class methods vs class fields that hold arrows?
- Do you know plain bare calls like
f()yieldundefinedin strict mode (modules and classes), not the global object?
A memorized slogan without a runnable mental model falls apart on the whiteboard.
Core mental model: call-site decides this
For ordinary functions (not arrows), this is determined when the function runs, from the call form:
- Method call:
obj.fn()→thisis obj (the receiver left of the dot) - Bare call:
fn()→thisisundefinedin strict mode; in sloppy mode it can be the global object - Explicit:
fn.call(receiver)/fn.apply(receiver, args)→thisis receiver for that invocation - Bound function: a function from
fn.bind(receiver)→thisis stuck to receiver when you call it - Construct:
new Fn()→thisis the newly created object (unless the constructor returns another object)
Do not say "this is always the object the function is defined on." Definition site does not set this for ordinary functions. The call site does.
const user = {
name: "Nicola",
greet() {
return `hi ${this.name}`;
},
};
user.greet(); // this === user → "hi Nicola"
const f = user.greet;
f(); // bare call: this is undefined (strict) or global (sloppy)call, apply, and bind
call and apply invoke the function once with a chosen this. call takes arguments as a list. apply takes them as an array (or array-like).
bind returns a new function whose this is fixed (and you can partially apply leading arguments). Calling the bound function later does not re-decide this from a new call site.
function intro(role) {
return `${this.name} (${role})`;
}
const person = { name: "Nicola" };
intro.call(person, "engineer"); // "Nicola (engineer)"
intro.apply(person, ["engineer"]); // same idea, args as array
const bound = intro.bind(person, "engineer");
bound(); // still this === person
bound.call({ name: "other" }); // still "Nicola (engineer)": bind wins over callBinding is permanent: call, apply, and even a second bind cannot change the this of a bound function. The one exception is new, which ignores the bound this and uses the newly created object.
Interview signal: use call/apply when you need one shot with a receiver. Use bind when you need a stable function value (passing a method into an API that will call it later).
Arrow functions: lexical this, not call-site this
Arrow functions do not have their own this binding. They close over this from the enclosing lexical scope (the same way they close over other variables). Method call syntax does not give an arrow a new this. call, apply, and bind also cannot change an arrow's this.
Method shorthand (greet() { ... }) is an ordinary function. It is not an arrow. Writing greet: () => { ... } on an object is an arrow property, so obj.greet() still sees the outer this, which is usually wrong for a method.
const outerThis = this;
const obj = {
name: "box",
regular() {
return this.name;
},
arrow: () => {
// lexical this (outer), not obj
return this === outerThis;
},
};
obj.regular(); // "box"
obj.arrow(); // true: arrow sees outer this, not objClassic gotcha: method as callback
Passing obj.method as a callback hands over only the function, not the receiver. The caller decides this, and it is never your object unless you keep the method call, bind, or pass it as thisArg.
What the caller picks depends on the API. Browser setTimeout calls it with the global object (WindowProxy), even in strict mode. Node's setTimeout uses its Timeout object. addEventListener uses the element the listener is attached to (event.currentTarget). Only plain calls like f() and array callbacks with no thisArg give undefined in strict mode.
class Counter {
constructor() {
this.n = 0;
}
inc() {
this.n += 1;
}
}
const c = new Counter();
const extracted = c.inc;
// extracted(); // TypeError in class bodies (strict): cannot read 'n' of undefined
setTimeout(c.inc, 0); // loses c: in a browser this runs window.n += 1 (NaN, no error)
setTimeout(() => c.inc(), 0); // wrapper keeps the method call form
setTimeout(c.inc.bind(c), 0); // bind freezes this to cSay out loud: the bug is not "setTimeout is weird." The bug is handing over a method without its receiver, so the caller picks this, and the caller won't pick your object by default.
Classic gotcha: new binding
new Fn() creates an object, sets its [[Prototype]], binds this to that object for the constructor run, and returns the object (unless the constructor explicitly returns a different object).
function Person(name) {
this.name = name;
}
const p = new Person("Nicola");
p.name; // "Nicola"Forgetting new on a constructor that assigns to this is a common fail: in strict mode you get a throw on assigning to undefined; in sloppy mode you may pollute the global object. Prefer class syntax or guard constructors when that matters.
Classic gotcha: nested functions and class fields
A nested ordinary function inside a method gets its own this from its own call site, not from the outer method. That is why people reach for const self = this, .bind(this), or an arrow for the inner callback.
const cart = {
prefix: "item:",
items: ["a", "b"],
broken() {
this.items.forEach(function (x) {
console.log(this.prefix + x); // this is not cart (undefined in strict mode)
});
},
works() {
this.items.forEach((x) => console.log(this.prefix + x)); // arrow: this is cart
},
};forEach also accepts a thisArg (this.items.forEach(function (x) { ... }, this)), but the arrow is the usual fix.
Class fields that assign an arrow (inc = () => { this.n++; }) capture the instance as lexical this when the instance is created. That can make callback-passing ergonomic. Prototype methods (inc() { ... }) stay shared on the prototype and still need a method call or bind when extracted. Know the tradeoff: arrows-as-fields are per-instance functions; methods are shared.
class Timer {
constructor() {
this.secs = 0;
}
// shared on prototype; loses this if extracted
tick() {
this.secs += 1;
}
// per-instance arrow field; lexical this is the instance
tickBound = () => {
this.secs += 1;
}
}
const t = new Timer();
setTimeout(t.tick, 0); // broken: this is not t
setTimeout(t.tickBound, 0); // works: arrow closed over tWhat to say out loud in an interview
A strong answer sounds like this:
- For ordinary functions,
thiscomes from the call site: method receiver, bare call, explicit call/apply, bind, or new. - Extracting a method (
const f = obj.fn) loses the receiver. Callf()andthisisundefinedin strict mode; pass it as a callback and the caller picksthis, which won't be your object by default. - call/apply set
thisfor one call; bind returns a new function withthisfixed permanently. - Arrows have no own
this; they close over lexicalthis. Method shorthand is not an arrow. - In strict mode (modules, classes), plain bare calls get
undefined, not window/global. Callback APIs choose their ownthis: a DOM handler gets the element (event.currentTarget).
Then walk one small demo: method call, extracted bare call, then fix with bind or a wrapper arrow.
Common wrong answers to avoid
- "
thisis always the object the function is defined on." No. Call site decides for ordinary functions. - "Arrow functions are just shorter methods." No. Arrows do not bind
thisfromobj.fn()style calls. - "bind and call are the same." No. call invokes now; bind returns a new function.
- "In the browser,
thisis always window." No. Method calls use the receiver, strict plain calls getundefined(modules are strict), and in a DOM handlerthisis the element (event.currentTarget). - "I need bind on every class method." No. Only when the method is extracted or passed as a callback.
- "new just sets the prototype." Incomplete. new also creates the object and binds
thisfor the constructor. - "The wrapper arrow works because greet borrows the arrow's
this." No. The inner callobj.greet()is still a method call on obj, so obj is the receiver. The arrow only delays the call.
Takeaways
- Predict
thisfrom the call form, not from where the function was written. - Method extraction is the interview landmine; fix it with bind, or a wrapper arrow
() => obj.fn()that keeps the method call. - call/apply = one-shot
this; bind = stablethis(and optional partial args). - Arrows close over lexical
this; do not use them when you want method-style receivers. - Strict mode makes a lost
thisfail loudly (undefined) only for true bare calls likef(). Callers likesetTimeoutcan hand you the global object anyway:setTimeout(c.inc)in a browser quietly writeswindow.n.
If you can narrate call-site rules, the extraction trap, bind vs call, and arrow lexical this with a tiny demo, you are interview-ready on this.
Hire Nicola to build your app
Nicola builds apps for clients: scoping the work, shaping the experience, and shipping systems that fit. See pricing or get in touch if you have a product, site, or front-end system in mind.
About the author
Nicola Riker is an Awwwards-nominated design engineer based in Colorado Springs. She plans, designs, and builds digital products for clients.
Keep going: closures, the event loop, promises, and prototypes are covered in earlier notes on this blog.