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
4 Rules That Decide What this Means in JavaScript (call, apply, bind)
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 this inside this method? What if we extract the method and call it alone?
  • Fix a bug where a click handler or setTimeout loses 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 this and do not get their own this?
  • Can you contrast class methods vs class fields that hold arrows?
  • Do you know plain bare calls like f() yield undefined in 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() → this is obj (the receiver left of the dot)
  • Bare call: fn() → this is undefined in strict mode; in sloppy mode it can be the global object
  • Explicit: fn.call(receiver) / fn.apply(receiver, args) → this is receiver for that invocation
  • Bound function: a function from fn.bind(receiver) → this is stuck to receiver when you call it
  • Construct: new Fn() → this is 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.

JavaScript
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.

JavaScript
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 call

Binding 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.

JavaScript
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 obj

Classic 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.

JavaScript
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 c

Say 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).

JavaScript
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.

JavaScript
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.

JavaScript
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 t

What to say out loud in an interview

A strong answer sounds like this:

  1. For ordinary functions, this comes from the call site: method receiver, bare call, explicit call/apply, bind, or new.
  2. Extracting a method (const f = obj.fn) loses the receiver. Call f() and this is undefined in strict mode; pass it as a callback and the caller picks this, which won't be your object by default.
  3. call/apply set this for one call; bind returns a new function with this fixed permanently.
  4. Arrows have no own this; they close over lexical this. Method shorthand is not an arrow.
  5. In strict mode (modules, classes), plain bare calls get undefined, not window/global. Callback APIs choose their own this: 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

  • "this is 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 this from obj.fn() style calls.
  • "bind and call are the same." No. call invokes now; bind returns a new function.
  • "In the browser, this is always window." No. Method calls use the receiver, strict plain calls get undefined (modules are strict), and in a DOM handler this is 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 this for the constructor.
  • "The wrapper arrow works because greet borrows the arrow's this." No. The inner call obj.greet() is still a method call on obj, so obj is the receiver. The arrow only delays the call.

Takeaways

  • Predict this from 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 = stable this (and optional partial args).
  • Arrows close over lexical this; do not use them when you want method-style receivers.
  • Strict mode makes a lost this fail loudly (undefined) only for true bare calls like f(). Callers like setTimeout can hand you the global object anyway: setTimeout(c.inc) in a browser quietly writes window.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.