Back to JavaScript
JavaScript
medium
very high
mid

Can you explain how this, call, apply, and bind relate in JavaScript?

`this` is determined at call time by how the function is invoked. `call`/`apply` invoke immediately with a chosen `this`; `bind` returns a new function permanently bound to it.

6 min read·~12 min to think through

Of all the topics that trip up JavaScript candidates, this ranks #1 — and the reason is that the rules look ad-hoc until you internalize one core idea: this is resolved at the call site, not at the definition site (for regular functions). Arrow functions are the deliberate exception: they capture this lexically, the way variables do.

The four binding rules, in strict priority order:

  1. new binding. new Foo(...) creates a brand-new object, sets this to it, runs the constructor body, and (unless the constructor returns its own object) returns the new object. Highest priority — overrides everything else.
  1. Explicit binding. fn.call(ctx, a, b) invokes fn immediately with this === ctx and a, b as arguments. fn.apply(ctx, [a, b]) is the same but takes arguments as an array — useful when you don't know the arity statically. fn.bind(ctx, a) returns a new function permanently bound to ctx with a partially applied. bind cannot be re-bound; the first bind sticks. With new, the bound this is ignored.
  1. Implicit binding. When called as a method (obj.fn()), this is the object the function was accessed from. Crucially, the binding lives at the call site, not the reference: const f = obj.fn; f() loses the binding because the method invocation form is gone. This is the source of countless "lost this" bugs when handlers are detached and passed around.
  1. Default binding. No new, no explicit call/apply/bind, no method invocation → this falls back to the global object (window / globalThis) in non-strict mode, or undefined in strict mode (and inside ES modules and class bodies, which are strict by default).

Arrow functions ignore all four rules. Arrows do not have their own this; lookup falls through to the enclosing function's this at the moment the arrow was defined. That's why obj.method = () => this is almost always a bug — it captures the outer scope's this (which, in a module, is undefined), not obj. Conversely, arrow callbacks are perfect for situations where you want this to inherit: array.map(x => this.transform(x)) inside a method does the right thing without bind.

Call vs apply vs bind — when to use which:

  • call: known argument count, spread inline. fn.call(ctx, a, b, c).
  • apply: argument array. Common before ES6 spread for forwarding arguments: fn.apply(this, arguments). With ES6, fn.call(this, ...arguments) is equivalent.
  • bind: you need a long-lived reference with this locked in (event handlers, callbacks passed to libraries that don't preserve context). Also enables partial application: fn.bind(ctx, a) is a new function that always prepends a.

Concrete examples that interviewers ask about:

js
const user = { name: 'Ada', hi() { return \`Hi, \${this.name}\`; } };
user.hi();                  // "Hi, Ada" — implicit binding
const f = user.hi; f();     // "Hi, undefined" (strict) — binding lost
user.hi.call({name:'Bob'}); // "Hi, Bob" — explicit binding wins
const bound = user.hi.bind({name:'Eve'});
bound();                    // "Hi, Eve"
new (function() { console.log(this); })(); // {} new instance — new binding

Class methods. Methods on the prototype are not auto-bound. const fn = instance.method; fn() loses this. Two common fixes: bind in the constructor (this.method = this.method.bind(this)), or use arrow class fields (method = () => {...}). Both create per-instance allocations — fine for typical UI components, but worth knowing for tight allocation patterns.

this in callbacks is the classic React 15 pain: <button onClick={this.handleClick}> lost this, so you wrote onClick={this.handleClick.bind(this)} (creates a new function every render, breaks React.memo) or arrow class fields. With hooks and function components, the problem disappears — there's no this to lose.

Pitfalls and edge cases:

  • bind is one-shot. fn.bind(a).bind(b) is still bound to a — the second bind is ignored for this (it can still add partial args).
  • bind with new ignores the bound this and creates a fresh instance.
  • Strict mode default — top-level this is undefined in modules; legacy scripts get window.
  • Method extraction (document.addEventListener('click', this.handler)) loses this unless you bind or use arrow fields.
  • Arrow functions in object literals{ greet: () => this.name } captures the enclosing this, not the object.
  • Each bind allocates a new function — avoid in hot render paths.

The summary for interviews. "Regular functions resolve this at the call site by one of four rules in priority order: new, explicit (call/apply/bind), implicit (method call), default. Arrow functions ignore all four and inherit this lexically. call and apply invoke immediately; bind returns a new function permanently bound to a context."

Code

ts
function greet(greeting: string, punct: string) {
  return `${greeting}, ${this.name}${punct}`;
}
const user = { name: "Ada" };

greet.call(user, "Hi", "!");          // "Hi, Ada!"  — args spread
greet.apply(user, ["Hi", "!"]);       // "Hi, Ada!"  — args as array
const bound = greet.bind(user, "Hi"); // partially applied
bound("?");                           // "Hi, Ada?"
All four side-by-side
ts
Function.prototype.myBind = function (ctx: any, ...preset: any[]) {
  const fn = this;
  return function (this: any, ...later: any[]) {
    // If called with 'new', honor that and ignore ctx.
    const calledWithNew = this instanceof (fn as any);
    return fn.apply(calledWithNew ? this : ctx, [...preset, ...later]);
  };
};
Polyfill of bind (the classic interview ask)

Follow-up questions

  • Why doesn't `this` work in an arrow function the way you might expect?
  • Implement Function.prototype.call from scratch.
  • How does class method binding interact with React event handlers?

Common mistakes

  • Detaching a method from its object (`const f = obj.method`) and expecting `this` to survive.
  • Using arrow functions as object methods or class prototype methods.
  • Calling `bind` repeatedly — only the first `bind` sticks; the second is ignored.

Performance considerations

  • Each `bind` allocates a new function — avoid in tight loops or hot render paths.
  • Class methods bound in the constructor allocate per-instance; arrow class fields allocate per-instance too. Prototype methods are shared — bind at the call site if you need stable identity.

Edge cases

  • `bind` cannot be re-bound — `fn.bind(a).bind(b)` is still bound to `a`.
  • Calling a bound function with `new` ignores the bound `this` and creates a new instance.

Real-world examples

  • React class components used to need `this.handler = this.handler.bind(this)` in the constructor before arrow class fields became common.
  • Array-likes use `Array.prototype.slice.call(arguments)` to convert to a real array (pre-ES6).

Senior engineer discussion

Senior signal: discuss the strict-mode differences for default `this`, why arrow functions in class fields create per-instance allocations, and how `Function.prototype.bind` is specified to mark the result with `[[BoundTargetFunction]]`.

Related questions