ESC

Type to search the knowledge base.

call, apply, and bind

Explicit this control — call vs apply vs bind, partial application, bound constructors, and when arrows make bind pointless.

intermediate6 min read
  • javascript
  • call
  • apply
  • bind
  • this

call, apply, and bind are how you set this on purpose for ordinary functions. They are not “alternatives to calling a function.” They are the explicit-binding rule in the this binding hierarchy.

  • fn.call(thisArg, ...args) — invoke fn now with given this and arguments.
  • fn.apply(thisArg, argsArray) — same, args as an array-like.
  • fn.bind(thisArg, ...partialArgs) — return a new function with this (and optional leading args) fixed.

If you treat them as interchangeable syntax sugar, you’ll misuse bind in hot paths and forget that arrows ignore them for this.

The problem they solve

Methods detach from their object the moment you pass them as a callback:

const user = {
  name: 'Ada',
  greet(prefix) {
    return `${prefix}, ${this.name}`;
  },
};

console.log(user.greet('Hi')); // "Hi, Ada"

const loose = user.greet;
// loose('Hi'); // TypeError or wrong this — depends on strict mode / environment

DOM handlers, setTimeout, React props (class era), and array methods all strip the method call shape obj.method(). Explicit binding puts this back.

call — invoke now, list of args

function intro(greeting, punctuation) {
  return `${greeting}, ${this.role}${punctuation}`;
}

const person = { role: 'engineer' };

intro.call(person, 'Hello', '!'); // "Hello, engineer!"

Use call when you already have discrete arguments. Also common when borrowing methods:

const arrayLike = { 0: 'a', 1: 'b', length: 2 };
const joined = Array.prototype.join.call(arrayLike, '-'); // "a-b"

Modern code often prefers Array.from(arrayLike).join('-') or spread when the value is iterable — but call still shows up in polyfills, interview tasks, and performance-sensitive borrowing.

apply — invoke now, args as array

const nums = [3, 9, 1];
Math.max.apply(null, nums); // 9

// Today:
Math.max(...nums);

Historically apply was how you spread before rest/spread existed. It still matters when:

  • You are implementing utilities that forward unknown arity
  • You must pass a true array-like without converting first
  • Interview whiteboards ban ... to force fundamentals
function logAll() {
  const args = Array.prototype.slice.apply(arguments);
  console.log(args);
}

Prefer rest parameters in new functions: function logAll(...args).

bind — return a bound function for later

const user = {
  name: 'Grace',
  greet(greeting) {
    return `${greeting}, ${this.name}`;
  },
};

const greetGrace = user.greet.bind(user, 'Hey');
greetGrace(); // "Hey, Grace"
setTimeout(user.greet.bind(user, 'Later'), 0);

bind does not call greet. It returns a new exotic function whose this is permanently user (for ordinary calls). Partial arguments are prepended:

function multiply(a, b) {
  return a * b;
}
const double = multiply.bind(null, 2);
double(5); // 10

bind vs re-wrapping

// bind
const b = user.greet.bind(user);

// manual wrap — similar this, different function identity each time you recreate
const w = (...args) => user.greet(...args);

Bound functions have a stable identity if you bind once and store the result. Re-creating bind every render (React class handlers) forces child pure-component props to change. Store bound methods once (constructor bind, class fields, or hooks patterns).

Precedence: bound functions are sticky

function show() {
  return this.id;
}
const objA = { id: 'A' };
const objB = { id: 'B' };

const boundA = show.bind(objA);
boundA.call(objB); // "A" — call cannot override bind's this
boundA.apply(objB); // "A"

Exception people remember for interviews: new boundFn() uses the new object as this for the underlying constructor path; bound thisArg is ignored under new. Rare in app code; common in trivia.

Soft bind / nullish thisArg (strict vs sloppy)

In sloppy mode, call/apply/bind with null or undefined as thisArg coerce this to the global object. In strict mode (and modules, which are strict), this stays null/undefined.

'use strict';
function f() {
  return this;
}
f.call(null); // null

Don’t rely on global coercion. Pass the object you mean.

Arrow functions ignore this binding

const obj = {
  id: 1,
  regular() {
    return this.id;
  },
  arrow: () => this.id, // lexical this from outer scope, not obj
};

obj.regular(); // 1
obj.arrow(); // likely undefined in modules

const bound = obj.arrow.bind({ id: 99 });
bound(); // still outer lexical this — bind does not rebind arrow `this`

Arrows close over this from the enclosing scope (closures + lexical this). Use arrows when you want that. Use ordinary functions + bind/call when the method must take this from the receiver.

Implementing a tiny bind (interview staple)

Function.prototype.myBind = function (thisArg, ...boundArgs) {
  const original = this;
  if (typeof original !== 'function') {
    throw new TypeError('Bind must be called on a function');
  }
  return function bound(...args) {
    return original.apply(thisArg, boundArgs.concat(args));
  };
};

function greet(g, p) {
  return `${g}, ${this.name}${p}`;
}
const hi = greet.myBind({ name: 'Lin' }, 'Hi');
hi('!'); // "Hi, Lin!"

A production-faithful bind also handles new, prototype linkage, and length/name. Interviewers usually want: store thisArg, prepend args, use apply, return a new function. Mentioning real bind edge cases scores extra points without implementing them.

When to use which

Tool Use when
call One-shot invoke with known args; method borrowing
apply One-shot invoke with array of args; legacy spread
bind Callback needs fixed this or partial args later
Arrow Want lexical this; no dynamic receiver
Direct obj.method() You still own the call shape

Debounce/throttle utilities use apply so wrapped methods keep the caller’s this — see Debounce Implementation and Throttle Implementation.

Footguns

  1. bind every render / every event attach — new function identity; hard to removeEventListener.
  2. Binding arrows — no-op for this; confuses readers.
  3. Forgetting partial args are leading — fn.bind(null, a) fixes first parameter.
  4. apply with huge arrays — older engines had argument count limits; spread/Math.max reductions can share that history.
  5. Assuming bind copies the function’s prototype methods you care about — you get a bound wrapper, not a clone of the function object’s own properties (except special bound exotic behavior).

Interview angle

Prompt: “Difference between call, apply, and bind?”

Strong answer: “call and apply invoke the function immediately with an explicit this; call takes a argument list, apply takes an array. bind returns a new function with this and optional leading arguments fixed for later calls. Bound this wins over later call/apply. Arrow functions don’t get their this from these APIs.”

Code task: Implement bind with partial args; explain why array.map(obj.method) breaks and how obj.method.bind(obj) fixes it.

Further reading

Related guides