Understanding call, apply, and bind in JavaScript

Abstract image used to represent call, apply, and bind in JavaScript
Image by Lewis Kang'ethe Ngugi.

In Brief

For an ordinary JavaScript function, call() and apply() invoke it with a supplied receiver, whilst bind() creates a function with a receiver and optional arguments preset. Arrow functions keep their own lexical this, so these methods cannot replace it.

For an ordinary function, JavaScript's this depends on how it is called. Arrow functions are different: they keep the this of the surrounding scope where they were created.

That distinction matters in real code reviews. You might see call, apply or bind in legacy modules, event handlers, callbacks, decorators, functional utilities, tests, mocks, or thirdparty library integrations. Modern JavaScript has reduced the need for manual binding in many daytoday cases, but it has not removed the need to understand context.

The mistake to avoid is using bind as a quick patch without asking why the context was lost. Sometimes binding is exactly the right fix. Sometimes it hides a muddled component boundary, an extracted method being passed around too freely or a callback that should be an arrow function.


What call, apply and bind do

These methods let us supply arguments and, for functions with their own dynamic this, a receiver. An arrow still accepts the arguments, but ignores the supplied receiver.

The difference is when the function runs and how its arguments are supplied.

call

call invokes the function immediately. You pass the this value first, then each argument separately.

function greet(greeting, name) {
  console.log(`${greeting}, ${name}! My context is:`, this);
}

greet.call({ role: 'developer' }, 'Hello', 'Maddie');
// Output: Hello, Maddie! My context is: { role: 'developer' }

apply

apply also invokes the function immediately, but it expects the arguments as an array or arraylike object.

const numbers = [5, 10, 15];

const max = Math.max.apply(null, numbers);
console.log(max);  // Output: 15

bind

bind() returns a new function without calling the original. For an ordinary call to that bound function, its receiver is fixed. Constructing it with new has a different rule, covered below.

const user = { name: 'Sophie' };

function introduce() {
  console.log(`My name is ${this.name}`);
}

const boundIntroduce = introduce.bind(user);
boundIntroduce();  // Output: My name is Sophie

That makes bind useful when you need a function reference, not an immediate result.


call in practice

Use call when you want to run a function now and provide the context directly.

It is useful for borrowed methods, arraylike objects and small cases where explicit invocation is clearer than wrapping the function in another function.

Syntax of call

functionName.call(thisArg, arg1, arg2, ...);
  • thisArg: The value to use as this inside the function.
  • arg1, arg2, ...: The arguments passed to the function one by one.

Borrowing methods with call

Borrowing a method is the classic example. The method belongs to one object or prototype, but you want to run it against another value.

const person = {
  firstName: 'John',
  lastName: 'Doe',
  fullName: function () {
    return `${this.firstName} ${this.lastName}`;
  },
};

const anotherPerson = { firstName: 'Jane', lastName: 'Smith' };

// Borrowing fullName method for anotherPerson
console.log(person.fullName.call(anotherPerson));  // Output: "Jane Smith"

This pattern appears less often in modern application code than it used to, but it still appears around arraylike values, DOM APIs, and old utility code.

Passing arguments with call

When the arguments are already known, call keeps the invocation direct.

function greet(greeting, punctuation) {
  console.log(`${greeting}, ${this.name}${punctuation}`);
}

const user = { name: 'Ellie' };

// Invoking greet with a custom `this` and arguments
greet.call(user, 'Hello', '!');  // Output: "Hello, Ellie!"

What to watch with call

  • A strict function preserves the supplied thisArg exactly, including null, undefined and primitive values.
  • An ordinary nonstrict function replaces null or undefined with the global object and wraps other primitive receivers. That can hide a missing receiver rather than fixing it.
  • If the argument list is already an array, apply or spread syntax may be clearer.

apply in practice

apply is close to call, but the argument list is supplied as an arraylike value.

That makes it useful when a function receives or constructs arguments dynamically.

Syntax of apply

functionName.apply(thisArg, [arg1, arg2, ...]);
  • thisArg: The value to use as this inside the function.
  • [arg1, arg2, ...]: An array or arraylike object containing the arguments.

Borrowing methods with apply

apply can borrow methods in the same way as call. The difference is the argument shape.

const numbers = [5, 10, 15, 20];

const maxNumber = Math.max.apply(null, numbers);
console.log(maxNumber);  // Output: 20

In this example, apply makes it easy to pass the array of numbers to Math.max.

Another apply example

function introduce(greeting, punctuation) {
  console.log(`${greeting}, my name is ${this.name}${punctuation}`);
}

const person = { name: 'Ellie' };
const args = ['Hello', '!'];

introduce.apply(person, args);  // Output: "Hello, my name is Ellie!"

What to watch with apply

  • Use apply when the arguments are already arraylike.
  • For ordinary arrays, spread syntax is often easier to read in modern JavaScript.
  • Do not use apply just to look clever. Explicit code usually wins in production reviews.

bind in practice

bind() fixes the receiver for ordinary calls to a function that has dynamic this. It can also preset arguments, including for an arrow whose lexical this will remain unchanged.

That is useful when a function will be called later by something else, such as an event listener, timer, callback, or library API.

Syntax of bind

const boundFunction = functionName.bind(thisArg, arg1, arg2, ...);
  • thisArg: The value to use as this inside the new function.
  • arg1, arg2, ...: Optional arguments that are preset when the new function is called.

Preserving context with bind

const user = {
  name: 'Maddie',
  greet: function () {
    console.log(`Hello, ${this.name}!`);
  },
};

const greetUser = user.greet.bind(user);

// Even if `greetUser` is called in a different context, `this` is preserved
greetUser();  // Output: "Hello, Maddie!"

This is the use case most developers recognise: a method is passed around, and you still need it to run with the original object as this.

Presetting arguments with bind

bind can also preset arguments. That creates a specialised function from a more general one.

function multiply(a, b) {
  return a * b;
}

const double = multiply.bind(null, 2);  // `a` is pre-set to 2
console.log(double(5));  // Output: 10

In this example, bind creates double by fixing the first argument of multiply to 2. Calling double(5) then calls multiply(2, 5).

What to watch with bind

  • A bound function is a new function reference.
  • Binding inside render paths or repeated setup code can create avoidable churn.
  • If an arrow function would make the ownership clearer, prefer the clearer option.
  • If the target is constructable, calling the bound function with new ignores its bound receiver and uses the newly created instance. Preset arguments are still passed through; binding an arrow does not make it constructable.

When to Use Each One

The short version is straightforward.

Use call for immediate invocation with listed arguments

const person = {
  name: 'John',
};

function introduce(age) {
  console.log(`Hi, I'm ${this.name} and I'm ${age} years old.`);
}

introduce.call(person, 30);
// Output: Hi, I'm John and I'm 30 years old.

call is useful when you want the invocation to be explicit and the argument list is known.

Use apply for immediate invocation with arraylike arguments

const numbers = [5, 1, 7, 3];

const maxNumber = Math.max.apply(null, numbers);
console.log(maxNumber);  // Output: 7

apply is useful when the arguments already exist as a list. In modern code, compare it with spread syntax before choosing it.

Use bind for delayed invocation

const printer = {
  prefix: 'Message:',
};

function printMessage(message) {
  console.log(`${this.prefix} ${message}`);
}

const boundPrinter = printMessage.bind(printer);

boundPrinter('Hello, World!');
// Output: Message: Hello, World!

bind is useful when another part of the system will call the function later and you need the context or initial arguments to stay fixed.


The Production Caveat

Arrow functions can reduce the need for manual binding when a callback should use the surrounding this. They are useful for that purpose, rather than a replacement for every method or constructor.

The caveat is that production code still contains older patterns, library callbacks and framework integration points. Understanding call, apply and bind helps you read that code without guessing.

It also helps you avoid the lazy fix. If this changes unexpectedly, first ask how the function is being called. The related article on why this changes in JavaScript event handlers and methods covers that failure mode directly.

Use these methods when they make invocation clearer. Avoid them when they make the code look more advanced than the problem deserves.


Untangling a delivery problem?

Send the symptoms, constraints, and affected routes. I'll help identify whether the issue sits in the application, platform, content model, deployment path, or search surface.