Understanding JavaScript's Call, Bind, and Apply Methods

himanshuhimanshu
13 min read

Table of contents

In JavaScript, functions are more than just building blocks of reusable code — they are first-class objects with powerful capabilities. Among these capabilities, three often overlooked methods — call(), apply(), and bind() — can completely change how a function behaves by manipulating its context, or this.

Whether you’re new to JavaScript or already building complex apps, understanding how to control a function’s execution context is a critical skill. In this article, we’ll take a deep dive into how call(), apply() and bind() work, when to use them and what pitfalls to avoid.

Let’s begin by revisiting the core concept that drives these methods: the mysterious and often misunderstood this keyword.

Understanding this in JavaScript

Before we dive into call(), apply(), and bind(), it’s crucial to understand the foundation they’re build on : the this keyword. In JavaScript, this refers to the object that is executing the current function — but what is actually points to depends entirely on how the function is called.

Let’s understand this using some examples of this in different scenarios:

  1. this in the Global Context

In non-strict mode:

console.log(this); // window (in browsers)

In strict mode:

'use strict';
console.log(this); // undefined
  1. this inside an Object Method

const user = {
    name: "Alice",
    greet() {
        console.log(`Hello, ${this.name}`)
    }
}

user.greet(); // Hello, Alice

Here, this refers to the user object, because the method is invoked using user.greet().

  1. this in a Standalone Function

function showName() {
    console.log(this.name);
}

const name = 'Global';
showName(); // undefined (in strict mode)

In a regular function not tied to an object, this defaults to:

  • window (or global) in non-strict mode

  • undefined in strict mode

  1. this in Arrow functions

Arrow functions do not have their own this. Instead, they lexically bind the this from their surrounding scope.

const person = {
    name: "Bob",
    greet: () => { console.log(this.name) }
};

person.greet(); // undefined

Here, this doesn’t refer to person, but to the outer lexical scope (usually window or undefined in strict mode).

Why This Matters

The entire purpose of call(), apply(), and bind() is to let you manually control what this refers to. Once you understand the rules of this, these tools become incredibly powerful for building more flexible and reusable code.

Introduction to call(), apply(), and bind()

In JavaScript, functions are not just code blocks — they’re objects and like all objects, they come with built-in methods. Three of the most powerful ones are:

  • call()

  • apply()

  • bind()

These methods allow you to explicitly set the this value when called or preparing to call a function. They’re particularly useful when:

  • You want to reuse a method from one object in another

  • You’re losing context inside a callback or setTimeout

  • You’re pre-setting parameters in a reusable way

How do they differ?

Here’s a quick intro we dive into each in detail:

sdMethodExecutes ImmediatelyAccepts ArgumentsReturns New Function
call()✅ YesAs individual args❌ No
apply()✅ YesAs an array❌ No
bind()❌ NoAs individual args✅ Yes

The Syntax

// call
func.call(thisArg, arg1, arg2, ...);

// apply
func.apply(thisArg, [arg1, arg2, ...]);

// bind
const newFunc = func.bind(thisArg, arg1, arg2, ...);

Real-Life Analogy

Think of a function like a tool (say, a screwdriver). Normally, it works with one specific screw (object). But call(), apply(), and bind() let you pick up the tool and use it on any object (screw) you want.

What is thisArg?

The first parameter in all three methods is the this context — i.e., the object you want the function to operate on. It can be:

  • A regular object

  • A primitive (will be converted to an object)

  • null or undefined (will default to global object or undefined in strict mode)

Now that we’ve introduced them, let's break each one down with real code examples, use cases, and edge cases.

Detailed Breakdown

a. call() Method

Calls a function with a specific this value and arguments password individually.

Syntax:

functionName.call(thisArg, arg1, arg2, ...);

Example:

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

const person = { name: "Himanshu" };

greet.call(person, 'Hello', "!"); // Hello, Himanshu!

Use Cases:

  • Borrowing methods from another object

  • Reusing generic functions across multiple contexts

const user1 = { name: 'Bob' };
const user2 = { name: 'Eva' };

function sayHi() {
  console.log(`Hi, I'm ${this.name}`);
}

sayHi.call(user1); // Hi, I'm Bob
sayHi.call(user2); // Hi, I'm Eva

b. apply() Method

Just like call(), but accepts arguments as an array or array-like object.

Syntax:

functionName.apply(thisArg, [arg1, arg2, ...]);

Example:

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

const person = { name: "Himanshu" };

greet.apply(person, ['Hi', '.']); // Hi, Himanshu.

Use Cases:

Useful when you already have arguments in array form.

Classic Use Case with Math.max:

const numbers = [2,4,21,1,15,4];
const max = Math.max.apply(null, numbers);
console.log(max);

c. bind() method

Returns a new function with a bound this value and optional preset arguments. Unlike call() and apply(), it does not invoke the function immediately.

Syntax:

const newFunction = functionName.bind(thisArg, arg1, arg2, ...);

Example:

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

const person = { name: "Himanshu" };

const boundGreet = greet.bind(person, 'Hey');
boundGreet('!'); // Hey, Himanshu!

Use Cases:

  • Preserving context in asynchronous code

  • Setting default parameters

  • Creating reusable bound functions

Using bind() in setTimeout:

const timer = {
    name: "Timer",
    start() {
        setTimeout(function() { console.log(this.name) }, 1000);
    }
}

timer.start();

Fix with bind():

const timerFixed = {
    name: "Timer",
    start() {
        setTimeout(function() { console.log(this.name) }.bind(this), 1000)
    }
}

timerFixed.start();

Comparison Table

When learning call(), apply(), and bind(), the difference can be subtle at first glance. This table lays out the key characteristics of each method:

Featurecall()apply()bind()
ExecutionImmediatelyImmediatelyReturns a new function
Arguments FormatPassed individually (arg1, arg2)Passed as a single array ([arg1, arg2])Passed individually (arg1, arg2)
ReturnsReturn value of the functionReturn value of the functionA new function with bound context
Can Set this Context✅ Yes✅ Yes✅ Yes
Used for Method Borrowing✅ Often✅ Sometimes✅ Sometimes
Used for Currying/Partial App✅ Yes
Common Use CaseDirectly invoking with custom thisInvoking with arguments in array formCreating bound functions for later use
Arrow Function CompatibilityNot applicable (arrow functions don’t use their own this)Same as call()Same as call() but useful in event callbacks

Real-World use Cases

Understanding syntax is important, but seeing how call(), apply(), and bind() solve real problems is what makes the knowledge stick. Here are some everyday scenarios where these methods shine:

  1. Borrowing Methods from Arrays

    Let’s say you have an array-like object (like arguments or a NodeList), and you want to use array methods on it:

function logArguments() {
    const args = Array.prototype.slice.call(arguments);
    console.log(args);
}

logArguments(1,2,3) // [1,2,3]

Here, Array.prototype.slice is borrowing using call() to convert arguments into a true array.

  1. Setting Context in Event Handlers

const button = {
    label: "Save",
    handleClick() {
        console.log(`Button clicked: ${this.label}`);
    }
}

const el = document.querySelector(`button`);
el.addEventListener('click', button.handleClick.bind(button));

Without bind(), this.label would be undefined because this would refer to the DOM element, not the button object.

  1. Reusing Functions Across Objects

function introduce(city) {
    console.log(`Hi, I'm ${this.name} from ${city}.`);
}

const user = {name: "Rahul"};
introduce.call(user, 'Mumbai'); // Hi, I'm Rahul from Mumbai.

You can reuse a generic function across any object with custom context using call().

  1. Passing Arguments as Arrays

function sum(a, b, c) {
    return a + b + c;
}

const numbers = [1,2,3];
const result = sum.apply(null, numbers);
console.log(result); // 6

This is cleaner and more flexible than unpacking arrays manually.

  1. Partial Application with bind()

You can pre-fill some arguments and get a reusable function:

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

const double = multiply.bind(null, 2);
console.log(double(5)); // 10

This is useful for currying or creating specialized versions of general funcions.

  1. Fixing this Inside setTimeout

const person = {
    name: 'Meena',
    greet() {
        setTimeout(function() {console.log(`Hello, ${this.name}`)}.bind(this), 1000);
    }    
};

person.greet(); // Hello, Meena

Without bind(), this.name would be undefined.

These examples show how call(), apply() and bind() aren’t just theoretical — they’re used in UI code, DOM events, functional utilities, and legacy code conversion.

Edge Cases and Gotchas

Even seasoned developers run into trouble with call(), apply(), and bind() if they’re not aware of a few quirks. Here are some important things to watch out for:

⚠️ 1. Arrow Functions Ignore call, apply, and bind

Arrow functions don’t have their own this. Instead, they inherit this from their surrounding lexical scope. This means call(), apply(), and bind() have no effect on them.

const user = {
    name: 'John',
    greet: () => {console.log(this.name)}
}

user.greet.call({name: 'Alice'}); // undefined

Even though call() is used, this.name is still undefined because arrow functions don’t rebind this.

Best Practice: Only use call, apply, or bind with regular (non-arrow) functions when you need to control this.

⚠️ 2. Double Binding Doesn’t Work

If a function is already bound with bind(), re-binding it again won’t change the this value.

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

const boundGreet = greet.bind({name: 'Himanshu'});
const reBoundGreet = boundGreet.bind({name: "Ashu"});

reBoundGreet(); // Himanshu

Only the first bind() takes effect.

⚠️ 3. bind() and Constructors Don’t Mix Well

You can’t use bind() to change this in a constructor function when using new.

function Person(name) {
    this.name = name;
}

const BoundPerson = Person.bind({});
const person = new BoundPerson('Himanshu');

console.log(person.name); // Himanshu

Even though we used bind({}), this still points to the newly created instance. JavaScript overrides the bound this in constructor calls.

⚠️ 4. Performance Considerations

Creating bound functions inside loops or frequently re-binding can lead to memory overhead or unexpected behavior.

for (let i = 0; i < 1000; i++) {
    el.addEventListener('click', handler.bind(obj)); // ⚠️ Creates 1000 different bound functions
}

Tip: Reuse bound functions instead of recreating them on each call.

⚠️ 5. null or undefined as thisArg

Passing null or undefined as the thisArg causes the function to default to:

  • The global object (window in browsers) in non-strict mode

  • undefined in strict mode

function show() {
    'use strict';
    console.log(this); // undefined
}

show.call(null); // undefined in strict mode

✅ Always be aware of the execution context and whether strict mode is enabled.

These edge cases are common in debugging and interviews. Knowing them not only improves your understanding but also helps you write safer, more predictable code.

Interactive Code Examples

Sometimes, the best way to understand how call(), apply(), and bind() work is by playing with them in code. Here are some examples you can copy and run in any online JS editor:

🧪 Example 1: Using call() for Method Borrowing

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

const person2 = {
  name: 'Kriti'
};

person1.greet.call(person2); // Hello, I'm Kriti

Try changing person2.name to see how call() affects this.

🧪 Example 2: Using apply() with Arrays

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

const max = Math.max.apply(null, numbers);
console.log(max); // 9

Modify the array to see how it affects the result.

🧪 Example 3: Fixing this with bind()

const dog = {
  name: 'Buddy',
  bark() {
    console.log(`${this.name} says woof!`);
  }
};

const button = document.querySelector('#myBtn');
button.addEventListener('click', dog.bark.bind(dog));

Without bind(), this.name would be undefined.

Tip: You can recreate this using a simple HTML button in CodePen.

🧪 Example 4: Partial Function with bind()

function greet(time, name) {
  console.log(`Good ${time}, ${name}!`);
}

const morningGreet = greet.bind(null, 'morning');
morningGreet('Priya'); // Good morning, Priya!

Change ‘morning‘ to ‘evening‘ or ‘afternoon‘ to make it dynamic.

🧪 Example 5: Custom Implementation Challenge (for advanced users)

Try to implement your own version of bind():

Function.prototype.myBind = function(context, ...args) {
  const fn = this;
  return function(...moreArgs) {
    return fn.apply(context, [...args, ...moreArgs]);
  };
};

function sayName(greeting) {
  console.log(`${greeting}, I'm ${this.name}`);
}

const person = { name: 'Nina' };
const customBound = sayName.myBind(person);
customBound('Hello'); // Hello, I'm Nina

Interview-Style Questions

These questions range from basic to advanced and are designed to challenge the reader’s understanding of call(), apply() and bind() as well as their knowledge of function context in general.

  1. What is the difference between call(), apply() and bind()?

A common starter question to test knowledge of syntax and use cases.

Expected Answer:

  • call() invokes the function with a specific this and arguments passed one by one.

  • apply() is like call() but takes arguments as an array.

  • bind() returns a new function with a bound this, without calling it immediately.\

  1. How would you borrow a method from one object and use it in another?

Example Question:

const obj1 = { name: 'Sara' };
const obj2 = {
  name: 'David',
  greet() {
    console.log(`Hello, I'm ${this.name}`);
  }
};

// Use obj2.greet with obj1

Expected Answer:

obj2.greet.call(obj1); // Hello, I'm Sara
  1. What happens if you use bind() on an arrow function?

Expected Answer:

Arrow functions do not have their own this, so binding has no effect. this remains what it was in the surrounding lexical scope.

  1. Implement a polyfill for bind()

Expected Answer:

Function.prototype.myBind = function (context, ...args) {
    const fn = this;
    return function (...args) {
        return fn.apply(context, [...args, ...moreArgs])
    }
}
  1. When would you use apply() over call()?

Expected Answer:

When your arguments are in the form of an array or array-like object, such as arguments, apply() is more convenient and cleaner.

  1. What will be the output?

const person = {
    name: 'Jay',
    sayHi() {
        return () => { console.log(this.name) }
    }
}

const greet = person.sayHi();
greet.call({name: 'Himanshu'});

Answer:

Jay — because the returned function is an arrow function and this is lexical bound to person.

  1. How does bind() help in asynchronous code like setTimeout?

Expected Answer:

In setTimeout, the context (this) is often lost. bind() ensures that the original context is preserved when the function is executed later.

  1. Can you rebind a function that’s already bound?

Answer:

No. Once a function is bound using bind(), its this cannot be changed by re-binding.

These questions not only solidify concepts but also demonstrate practical understanding in interviews and real projects.

Conclusion

Mastering call(), apply(), and bind() in JavaScript is a game-changer—especially when you're working with complex applications, callbacks, event handlers, or third-party APIs.

Here’s a quick recap:

  • 🔄 call() — Instantly calls a function with a specified this and individual arguments.

  • 📦 apply() — Just like call(), but arguments are passed as an array.

  • 🔗 bind() — Returns a new function with a bound this, useful for callbacks, event listeners, and currying.

These methods allow you to take full control over the execution context of your functions and help eliminate bugs caused by misaligned this references.

Whether you're just starting out or brushing up for interviews, understanding and practicing these methods will sharpen your functional programming skills and make your JavaScript code more predictable and reusable.

Further Reading and Resources

To keep learning and explore related topics, here are some trusted resources:

📘 Official Documentation:

🧠 Advanced Topics to Explore Next:

  • Closures and Lexical Scoping in JavaScript

  • Custom Bind Polyfills and Curry Functions

  • Context Loss in Asynchronous JavaScript

  • The Role of this in ES6 Classes and Modules

🛠️ Practice Platforms:

🎯 Your Turn: Try writing a few utility functions using bind(), or re-implement your existing callbacks using call() to better manage context. You'll see how these simple methods can make your code more readable, elegant, and bug-free.

0
Subscribe to my newsletter

Read articles from himanshu directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

himanshu
himanshu