Call(), apply() and bind() methods in JavaScript

Episode - Call, Apply & Bind Methods in JavaScript

Hey everyone! Welcome back to the JavaScript deep dive series. Today we are going to learn about three very important methods — call(), apply(), and bind()!

These are must-know concepts for interviews and real-world development. They all deal with controlling what this refers to in JavaScript.

What we will cover:

  • The Problem — Why do we need call, apply, bind?
  • Understanding this keyword
  • call() Method
  • apply() Method
  • bind() Method
  • Difference Between call, apply, and bind
  • Real-World Use Cases
  • Interview Questions

The Problem — Why Do We Need These Methods?

In JavaScript, the value of this depends on how a function is called, not where it is defined. This can lead to unexpected behavior!

const person = {
    name: "Shreyesh",
    greet: function() {
        console.log("Hello, I am " + this.name);
    }
};

person.greet();  // Hello, I am Shreyesh ✅

const greetFn = person.greet;
greetFn();  // Hello, I am undefined ❌

// Why? Because when we assign greet to a variable,
// 'this' no longer points to 'person' object!
// 'this' now points to the global object (window/global)

call(), apply(), and bind() let us explicitly set what this refers to!


Understanding the this Keyword

Before diving into call, apply, bind — let's understand this:

Rules of 'this':
=================

1. In a method       → 'this' = the object that owns the method
2. Alone / function  → 'this' = global object (window in browser)
3. In strict mode    → 'this' = undefined
4. In arrow function → 'this' = inherited from parent scope
5. With call/apply/bind → 'this' = whatever YOU set it to! ✅

call() Method

call() invokes a function immediately and lets you set the value of this. Arguments are passed individually (comma-separated).

Syntax:

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

Example 1 — Basic call():

const person1 = { name: "Shreyesh" };
const person2 = { name: "John" };

function greet() {
    console.log("Hello, I am " + this.name);
}

greet.call(person1);  // Hello, I am Shreyesh
greet.call(person2);  // Hello, I am John

// We are telling JavaScript:
// "Run greet(), but set 'this' to person1 / person2"

Example 2 — call() with Arguments:

const person1 = { name: "Shreyesh" };
const person2 = { name: "John" };

function greet(city, country) {
    console.log(`Hello, I am ${this.name} from ${city}, ${country}`);
}

greet.call(person1, "Pune", "India");
// Hello, I am Shreyesh from Pune, India

greet.call(person2, "New York", "USA");
// Hello, I am John from New York, USA

// Arguments are passed INDIVIDUALLY after thisArg

Example 3 — Function Borrowing with call():

const employee = {
    name: "Shreyesh",
    getDetails: function(role, company) {
        return `${this.name} - ${role} at ${company}`;
    }
};

const freelancer = { name: "John" };

// Borrowing getDetails from employee for freelancer!
const result = employee.getDetails.call(freelancer, "Designer", "Freelance");
console.log(result);  // John - Designer at Freelance

// freelancer doesn't have getDetails method,
// but we BORROWED it from employee using call()!

apply() Method

apply() works exactly like call(), but arguments are passed as an array instead of individually!

Syntax:

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

Example 1 — Basic apply():

const person1 = { name: "Shreyesh" };

function greet(city, country) {
    console.log(`Hello, I am ${this.name} from ${city}, ${country}`);
}

// call() — arguments individually
greet.call(person1, "Pune", "India");

// apply() — arguments as an ARRAY
greet.apply(person1, ["Pune", "India"]);

// Both produce the SAME output!
// Hello, I am Shreyesh from Pune, India

Example 2 — apply() with Math.max:

const numbers = [5, 10, 3, 8, 15, 2];

// Problem: Math.max doesn't accept arrays!
// Math.max(5, 10, 3, 8, 15, 2) ← wants individual args

// Solution: Use apply() to spread the array!
const max = Math.max.apply(null, numbers);
console.log(max);  // 15

// null because Math.max doesn't use 'this'
// apply() spreads the array into individual arguments!

// Modern alternative: Spread operator
const max2 = Math.max(...numbers);  // Same result!

Example 3 — apply() for Array Merging:

const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];

// Push all elements of arr2 into arr1
Array.prototype.push.apply(arr1, arr2);
console.log(arr1);  // [1, 2, 3, 4, 5, 6]

// apply() spreads arr2 as individual arguments to push()
// Equivalent to: arr1.push(4, 5, 6)

bind() Method

bind() does NOT invoke the function immediately. Instead, it returns a new function with this permanently set to the provided value. You can call this new function later!

Syntax:

const newFunction = functionName.bind(thisArg, arg1, arg2, ...);
newFunction();  // Call it later!

Example 1 — Basic bind():

const person1 = { name: "Shreyesh" };

function greet() {
    console.log("Hello, I am " + this.name);
}

// call() → Invokes IMMEDIATELY
greet.call(person1);  // Hello, I am Shreyesh

// bind() → Returns a NEW function, call it LATER
const greetShreyesh = greet.bind(person1);
greetShreyesh();  // Hello, I am Shreyesh
greetShreyesh();  // Hello, I am Shreyesh (can call multiple times!)

Example 2 — bind() with Arguments (Partial Application):

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

// Create a function that always multiplies by 2
const double = multiply.bind(null, 2);
console.log(double(5));   // 10
console.log(double(10));  // 20

// Create a function that always multiplies by 3
const triple = multiply.bind(null, 3);
console.log(triple(5));   // 15
console.log(triple(10));  // 30

// This is called PARTIAL APPLICATION!
// We "pre-fill" the first argument using bind()

Example 3 — bind() in Event Handlers (Very Common!):

class Button {
    constructor(label) {
        this.label = label;
    }

    handleClick() {
        console.log(`${this.label} was clicked!`);
    }
}

const btn = new Button("Submit");

// ❌ Problem: 'this' is lost in event handlers!
// document.querySelector('#btn').addEventListener('click', btn.handleClick);
// Output: "undefined was clicked!" ← 'this' is the button element, not our object!

// ✅ Solution: Use bind()!
// document.querySelector('#btn').addEventListener('click', btn.handleClick.bind(btn));
// Output: "Submit was clicked!" ← 'this' is now correctly our object!

Example 4 — bind() with setTimeout:

const person = {
    name: "Shreyesh",
    greetLater: function() {
        // ❌ Without bind — 'this' is lost in setTimeout
        setTimeout(function() {
            console.log("Hello, " + this.name);  // Hello, undefined
        }, 1000);

        // ✅ With bind — 'this' is preserved!
        setTimeout(function() {
            console.log("Hello, " + this.name);  // Hello, Shreyesh
        }.bind(this), 1000);

        // ✅ Modern approach — Arrow function (inherits 'this')
        setTimeout(() => {
            console.log("Hello, " + this.name);  // Hello, Shreyesh
        }, 1000);
    }
};

person.greetLater();

Difference Between call(), apply(), and bind()

Feature call() apply() bind()
Invokes immediately? Yes Yes No (returns new function)
Arguments Comma-separated Array Comma-separated
Returns Function result Function result New function
Use case Invoke with custom this Invoke with array args Create reusable function
Easy Way to Remember:
======================

call()  → C for Comma      (args separated by commas)
apply() → A for Array       (args passed as array)
bind()  → B for Bind & Later (returns function, call later)

call()  → Calls immediately, args: individual
apply() → Calls immediately, args: array
bind()  → Does NOT call, returns new function

Side-by-side comparison:

const person = { name: "Shreyesh" };

function greet(city, country) {
    console.log(`${this.name} from ${city}, ${country}`);
}

// call — immediate, comma-separated args
greet.call(person, "Pune", "India");
// Output: Shreyesh from Pune, India

// apply — immediate, array args
greet.apply(person, ["Pune", "India"]);
// Output: Shreyesh from Pune, India

// bind — returns new function, call later
const greetShreyesh = greet.bind(person, "Pune", "India");
greetShreyesh();
// Output: Shreyesh from Pune, India

Real-World Use Cases

1. Function Borrowing (call/apply)

// Borrow Array methods for array-like objects
const arrayLike = { 0: "a", 1: "b", 2: "c", length: 3 };

// arrayLike is NOT a real array, but we can borrow Array methods!
const realArray = Array.prototype.slice.call(arrayLike);
console.log(realArray);  // ["a", "b", "c"]

// Common with 'arguments' object in functions
function example() {
    const args = Array.prototype.slice.call(arguments);
    console.log(args);  // Now it's a real array!
}

2. React Class Components (bind)

// In React class components, bind() is used extensively!
class App extends React.Component {
    constructor() {
        super();
        this.state = { count: 0 };

        // ✅ Binding 'this' in constructor
        this.handleClick = this.handleClick.bind(this);
    }

    handleClick() {
        this.setState({ count: this.state.count + 1 });
        // Without bind(), 'this' would be undefined!
    }

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

3. Partial Application / Currying (bind)

// Creating specialized functions from generic ones
function log(level, message) {
    console.log(`[${level}] ${message}`);
}

const logError = log.bind(null, "ERROR");
const logInfo = log.bind(null, "INFO");
const logDebug = log.bind(null, "DEBUG");

logError("Server crashed!");   // [ERROR] Server crashed!
logInfo("User logged in");     // [INFO] User logged in
logDebug("Variable x = 10");   // [DEBUG] Variable x = 10

4. API/Callback Context (bind)

const api = {
    baseUrl: "https://api.example.com",
    fetch: function(endpoint) {
        console.log(`Fetching: ${this.baseUrl}${endpoint}`);
    }
};

// ❌ Passing method as callback loses 'this'
// setTimeout(api.fetch, 1000, "/users");
// Fetching: undefined/users

// ✅ Using bind to preserve 'this'
setTimeout(api.fetch.bind(api), 1000, "/users");
// Fetching: https://api.example.com/users

Polyfill for bind() — Very Important for Interviews!

Interviewers love asking: "Can you write your own bind() function?"

// Custom bind() polyfill
Function.prototype.myBind = function(context, ...args) {
    // 'this' here refers to the function that myBind is called on
    const fn = this;

    return function(...newArgs) {
        return fn.apply(context, [...args, ...newArgs]);
    };
};

// Usage:
const person = { name: "Shreyesh" };

function greet(city, country) {
    console.log(`${this.name} from ${city}, ${country}`);
}

const greetShreyesh = greet.myBind(person, "Pune");
greetShreyesh("India");
// Output: Shreyesh from Pune, India

// How it works:
// 1. myBind saves the original function (fn = this)
// 2. Returns a NEW function
// 3. When new function is called, it uses apply()
//    to call the original function with the correct 'this'
// 4. Merges pre-filled args (...args) with new args (...newArgs)

Interview Questions

Q1: What is the difference between call(), apply(), and bind()?

"call() and apply() both invoke the function immediately with a specified 'this' context. The difference is call() takes arguments individually (comma-separated), while apply() takes them as an array. bind() does NOT invoke the function — it returns a new function with 'this' permanently bound, which you can call later."

Q2: What is function borrowing?

"Function borrowing is when you use call() or apply() to use a method from one object on another object. For example, using Array.prototype.slice.call(arguments) to convert the arguments object into a real array, or borrowing a method from one object to use with another without duplicating code."

Q3: Why do we use bind() in React class components?

"In React class components, event handler methods lose their 'this' context when passed as callbacks. We use bind(this) in the constructor to permanently bind the method's 'this' to the component instance. Without it, this.setState and this.state would be undefined inside the handler."

Q4: Can you write a polyfill for bind()?

"Yes. We add myBind to Function.prototype. It captures the original function and context, then returns a new function. When that new function is called, it uses apply() to invoke the original function with the saved context and merged arguments."

Q5: What is partial application using bind()?

"Partial application is when you use bind() to pre-fill some arguments of a function, creating a more specialized version. For example, multiply.bind(null, 2) creates a 'double' function that always multiplies by 2. The remaining arguments are provided when the new function is called."

Q6: What happens if you pass null or undefined as the first argument to call/apply/bind?

"In non-strict mode, 'this' will default to the global object (window in browser, global in Node.js). In strict mode, 'this' will be null or undefined as passed. This is why we pass null when we don't care about the 'this' context, like with Math.max.apply(null, array)."


Quick Recap

Concept Description
call() Invoke immediately, args comma-separated, sets 'this'
apply() Invoke immediately, args as array, sets 'this'
bind() Returns new function with 'this' bound, call later
Function Borrowing Use a method from one object on another using call/apply
Partial Application Pre-fill arguments using bind() to create specialized functions
Polyfill Custom implementation using Function.prototype and apply()

Key Points to Remember

  • call() = C for Comma (args individually)
  • apply() = A for Array (args as array)
  • bind() = B for Bind & Later (returns new function)
  • call and apply invoke immediately, bind does NOT
  • All three let you explicitly set 'this'
  • Function borrowing — use methods across objects
  • Partial application — pre-fill arguments with bind
  • bind() polyfill is a very common interview question
  • React class components use bind(this) in constructor
  • Arrow functions don't need bind — they inherit 'this' from parent

Keep coding, keep learning! See you in the next one!