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, settingthisand passing arguments comma-separated.apply(): Calls a function immediately, settingthisand passing arguments as an array.bind(): Does not call the function immediately; instead it returns a new function withthispermanently 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 setthisvalue 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
thiswhen the function runs. Passnullorundefinedto 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 tocall()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
applywhen your arguments are already in an array. Usecallwhen you have them individually. Use spread (...) in modern code when possible.
Syntax
func.apply(thisArg, [argsArray])
Here:
thisArg: Same ascall()- the value to use asthiscontext.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 andthisare 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: Thethisvalue 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
callandapplyis: Call is for Commas, Apply is for Arrays.
- JavaScript's
call(),apply(), andbind()give you explicit control over whatthismeans 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 withthisset 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?