JavaScript call, apply, bind methods

In this lesson you will learn call apply bind in JavaScript: three methods that directly test your understanding of the this keyword. Unlike most languages where this (or self) reliably refers to the current object, JavaScript's this is dynamic. It changes based on how a function is called, not where it is defined. If you need a refresher, you can read more in the dedicated article on the this keyword in JavaScript.

This single characteristic is responsible for a significant percentage of real bugs in JavaScript applications. Interviewers know this, so they use these three methods as a filter: developers who understand this context write better, more predictable code.

By the end, you will be able to clearly explain the difference between call, apply, and bind in JavaScript, answer common interview questions, and implement a basic bind polyfill from scratch.

What are call, apply, and bind in JavaScript?

At a high level:

  • call(): Calls a function immediately, setting this and passing arguments comma-separated.
  • apply(): Calls a function immediately, setting this and passing arguments as an array.
  • bind(): Does not call the function immediately; instead it returns a new function with this permanently set (and optionally some arguments pre-filled).

Before diving into the methods themselves, you need to understand the problem they solve.

The Core Problem: this loses its Context

In JavaScript, this is determined by how a function is called, not where it's defined.

const user = {
  name: "Alex",
  greet() {
    console.log(`Hello, I'm ${this.name}`);
  }
};

user.greet(); // "Hello, I'm Alex" -- works fine

const greet = user.greet;
greet(); // "Hello, I'm undefined" -- this is now global/undefined

When you extract user.greet into a standalone variable and call it, the connection to user is severed. this no longer points to the object. In strict mode it is undefined; in sloppy mode it falls back to the global object.

This is the exact scenario that call(), apply(), and bind() were built to fix.

Mental Model

To understand call, apply, and bind, the best mental model is to think of a "Borrowed Tool".

Imagine you have a Tool (a function) that performs an action, and a Storage Box (an object) that contains the supplies needed for that action.

Normally, a tool only works with the box it’s attached to. call, apply, and bind are ways to force that tool to work with a different box.

1. Setup this context

In JavaScript, the keyword this is like a pointer. By default, a function looks at its own "home" object to find data.

const chef = {
  specialty: "Pasta",
  cook() {
    console.log(`Cooking ${this.specialty}`);
  }
};

const amateur = { specialty: "Toast" };

If we want the chef.cook tool to work on the amateur's ingredients, we use these three methods: call, apply and bind.

2. call: the immediate invite

You are calling a professional to your house right now. You hand them your specific ingredients one by one.

  • Execution: Immediate.
  • Arguments: Passed individually (comma-separated).
chef.cook.call(amateur); // "Cooking Toast"

Think of it as: func.call(newOwner, arg1, arg2)Call = Comma separated.

3. apply: The Packed Suitcase

Same as call, but instead of handing the professional items one by one, you hand them a suitcase (an array) containing everything they need.

// If cook() took arguments like (sauce, garnish)
chef.cook.apply(amateur, ["Tomato", "Basil"]);

Think of it as: func.apply(newOwner, [args])Apply = Array.

4. bind: The Contract

You aren't cooking yet. You are signing a contract that says, "Whenever this tool is used in the future, it must use this specific box." It creates a brand-new function that is permanently linked to the new object.

const cookToast = chef.cook.bind(amateur);

// Later in the code...
cookToast(); // "Cooking Toast"

Think of it as: Bind = Bound for later.

call() method

call() invokes a function immediately with an explicitly set this value and individual arguments passed one by one.

Normally, the value of this inside a function is determined by how the function is called. With call, you override that behaviour and set this to whatever object you provide.

Syntax

func.call(thisArg, arg1, arg2, ...argN)
  • thisArg: The value to use as this when the function runs. Pass null or undefined to use the global object (or strict mode context).
  • arg1, arg2, ...argN: Arguments passed to the function individually, separated by commas.

Example: Borrowing method

The most practical everyday use for call() is method borrowing - using a method from one object on another object without copying it.

Converting array-like objects

call can apply array methods to array-like structures such as arguments. Here slice is borrowed from Array to convert arguments into a real array.

function printArgs() {
  // arguments is not a real array, but we can borrow Array's slice
  const argsArray = Array.prototype.slice.call(arguments);
  console.log(argsArray); // actual array
}

printArgs(1, 2, 3); // [1, 2, 3]

This is a real pattern you will see in older codebases. Modern code uses rest parameters (...args) instead, but understanding this pattern explains a lot of legacy JavaScript.

Borrowing methods from another object

A function defined on one object can be used with another object. greet belongs to person, but call() runs it with user as this.

const person = {
  greet() {
    console.log("Hello " + this.name);
  }
};

const user = { name: "Timur" };

// Borrow 'greet' from person and execute it with 'user' as the context (this)
person.greet.call(user);

Example: Chaining constructor

call() is also useful for constructor inheritance. When one constructor needs to initialize properties defined in another:

function Animal(name, sound) {
  this.name = name;
  this.sound = sound;
}

function Dog(name) {
  // Borrow Animal's constructor logic
  Animal.call(this, name, "Woof");
  this.species = "Canis lupus familiaris";
}

const rex = new Dog("Rex");
console.log(rex.name);  // "Rex"
console.log(rex.sound); // "Woof"

apply() method

apply() is functionally identical to call() with one key difference: arguments are passed as an array (or array-like object) rather than individually.

In modern JavaScript, the apply() method is often seen as a bit of an "old-style" tool because the Spread Syntax (...) has taken over many of its duties. However, understanding apply() is still crucial for maintaining legacy code and mastering how the this keyword works.

Rule of Thumb: Use apply when your arguments are already in an array. Use call when you have them individually. Use spread (...) in modern code when possible.

Syntax

func.apply(thisArg, [argsArray])

Here:

  • thisArg: Same as call() - the value to use as this context.
  • argsArray: A single array (or array-like object) containing all arguments for the function.

Example: Find min/max in Array

Before the spread operator existed, apply() was the only way to pass an array of numbers into functions that expected individual arguments, like Math.max() or Math.min().

Math.max()/Math.min() works only with individual parameters, e.g., Math.max(1, 3, 2).

const numbers = [5, 6, 2, 3, 7];

// Math.max(numbers) would return NaN because it doesn't take arrays
const max = Math.max.apply(null, numbers); 

console.log(max); // 7

In modern JavaScript, we usually write this with the spread operator instead of apply:

const maxWithSpread = Math.max(...numbers);

So why learn apply() at all?

  • It appears frequently in legacy code and older tutorials.
  • Understanding apply() gives you deeper intuition about how arguments and this are handled in JavaScript.

bind() method

bind() is where things get genuinely interesting. Unlike call() and apply(), bind() does not execute the function. It returns a new function with this permanently fixed to the value you specify.

Syntax

const boundFunction = func.bind(thisArg, arg1, arg2, ...argN)

Where:

  • thisArg: The this value that will be used every time the bound function is called.
  • arg1, arg2, ...argN (optional): Arguments to pre-fill. These get prepended to any arguments passed when the bound function is actually called.

bind() returns a new function. The original function is unchanged.

bind is often used when passing object methods as callbacks. Without bind, this inside the handler would point to the button element, not the object.

Example: Event Listeners and Callbacks

bind() shines when you need to pass a method as a callback but preserve its this context. Without bind(), passing a class method to an event listener loses the original object as this:

class FormHandler {  
  constructor(formId) {  
    this.formId = formId;  
    this.data = {};  
  }  
​  
  handleSubmit(event) {  
    event.preventDefault();  
    console.log(`Submitting form: ${this.formId}`);  
  }  
​  
  attachEvents() {  
    const form = document.getElementById(this.formId);  
    // Without bind -- `this` inside handleSubmit would be the DOM element  
    // With bind -- `this` stays as the FormHandler instance  
    form.addEventListener("submit", this.handleSubmit.bind(this));  
  }  
}

This is one of the most common real-world uses of bind() you will encounter in production JavaScript.

Example: bind() in React Class Components

If you work with older React codebases, you will see bind() everywhere in constructors. Before React hooks, class components required manual binding to keep event handlers working:

class Counter extends React.Component {
  constructor(props) {
    super(props);
    this.state = { count: 0 };
    // Without this, `this` inside handleClick would be undefined
    this.handleClick = this.handleClick.bind(this);
  }

  handleClick() {
    this.setState({ count: this.state.count + 1 });
  }

  render() {
    return <button onClick={this.handleClick}>{this.state.count}</button>;
  }
}

Modern React with hooks (useState, useEffect) eliminates this pattern entirely. But millions of lines of class component code exist in production, and knowing why bind(this) appears in constructors is essential for maintaining that code.

Common mistakes

Even experienced developers make mistakes with JavaScript this and with call, apply, and bind. Here are some of the most common ones.

1. Forgetting that this changes in callbacks

const counter = {
  value: 0,
  increment() {
    console.log(++this.value);
  }
};

setTimeout(counter.increment, 1000); // ❌ `this` is window/undefined, not counter

Fix with bind:

setTimeout(counter.increment.bind(counter), 1000); // ✅ prints 1

2. Expecting arrow functions to respect call, apply, or bind

Arrow functions do not have their own this, so call, apply, and bind cannot change their this value:

const obj = { value: 42 };

const arrow = () => console.log(this.value);
arrow.call(obj); // still logs undefined, not 42

Use a normal function when you need this to be dynamic and controlled with call, apply, or bind.

3. Confusing bind with immediate execution

bind() returns a new function instead of running it immediately:

function greet() {
  console.log("Hi " + this.name);
}

const user = { name: "Alex" };

greet.bind(user); // ❌ nothing logged yet
greet.bind(user)(); // ✅ now it executes

4. Overusing bind inside loops

Binding inside a tight loop creates a new function on every iteration:

items.forEach(item => {
  element.addEventListener("click", this.handleClick.bind(this)); // ❌ new bound function each time
});

Instead, create the bound function once:

const handleClickBound = this.handleClick.bind(this);
items.forEach(item => {
  element.addEventListener("click", handleClickBound); // ✅ reuse same function
});

On Interview

The common scenario in a JavaScript interview looks like this: they ask you to explain the difference between call, apply, and bind, then they show you a code snippet and ask what gets logged, then they ask you to implement bind() from scratch.

If you can handle all three, you look like a strong candidate. If you stumble on the polyfill, you look junior regardless of your framework experience.

Recap

Tip: A good way to remember the difference between call and apply is: Call is for Commas, Apply is for Arrays.

  • JavaScript's call(), apply(), and bind() give you explicit control over what this means inside any function. They solve a real and common problem: context loss when methods are extracted from objects or passed as callbacks.
  • call() invokes a function immediately with this set explicitly and arguments passed individually - ideal for method borrowing and constructor chaining.
  • apply() does the same but accepts arguments as an array - still useful in legacy environments, but largely replaced by the spread operator in modern JavaScript.
  • bind() returns a new bound function without executing it - the right tool for event listeners, callbacks, and partial application.

Here is a quick comparison of call, apply, and bind in JavaScript:

Method When it runs How arguments are passed Typical use cases
call Executes the function immediately func.call(thisArg, arg1, arg2, ...) — arguments are comma-separated Method borrowing, constructor chaining, quickly running a function with a specific this.
apply Executes the function immediately func.apply(thisArg, [args]) — arguments are in a single array/array-like Passing an array of arguments to a function that expects individual args (e.g. legacy Math.max.apply).
bind Does not execute immediately; returns a new function func.bind(thisArg, arg1, arg2, ...) — optional arguments are pre-filled Creating callbacks with fixed this (event handlers, timers, React class methods), simple partial application.

Next Steps

If you want to go deeper after mastering call apply bind in JavaScript, the next logical topics are:

Together they explain a large portion of how JavaScript actually executes code and why this behaves the way it does.

Practice Problems

1. Implement call() polyfill from scratch

You are required to implement custom call() polyfill from scratch that replicates its behavior.

2. Implement apply() polyfill from scratch

You are required to implement custom apply() polyfill from scratch that replicates its behavior.

3. Custom bind polyfill

Implement bind() polyfill from scratch

4. Fix the this issue

The interviewer is testing whether you understand one of JavaScript's most common bugs — losing this context inside callbacks. They want to see if you know how to use bind as a practical solution in real-world async code.

const timer = {
  seconds: 0,
  start() {
    setInterval(function() {
      this.seconds++; // ❌ `this` is window/undefined
      console.log(this.seconds);
    }, 1000);
  }
};

Fix with bind:

start() {
  setInterval(function() {
    this.seconds++;
    console.log(this.seconds);
  }.bind(this), 1000); // ✅
}

5. Borrowing Method Problem

The interviewer is testing if you understand one of the most practical uses of call — borrowing methods from one object to use on another without inheritance or copying.

This comes up a lot with array-like objects.

const dog = {
  name: "Rex",
  speak() { return `${this.name} says woof`; }
};

const cat = { name: "Whiskers" };

// Borrow dog's method for cat
console.log(dog.speak.call(cat)); // "Whiskers says woof"

6. Guess the output: Basic Syntax & Difference

The interviewer is testing your foundational understanding of how all three methods differ in syntax and behavior — the most basic but essential concept.

const person = { name: "Alice" };

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

greet.call(person, "Hello", "!");    // ?
greet.apply(person, ["Hi", "."]);   // ?

const boundGreet = greet.bind(person, "Hey");
boundGreet("?");                     // ?

Answer:

Hello, Alice!
Hi, Alice.
Hey, Alice?