Understanding JavaScript's Call, Bind, and Apply Methods

Table of contents
- Understanding this in JavaScript
- Introduction to call(), apply(), and bind()
- Detailed Breakdown
- Comparison Table
- Real-World use Cases
- Edge Cases and Gotchas
- Interactive Code Examples
- Interview-Style Questions
- What is the difference between call(), apply() and bind()?
- How would you borrow a method from one object and use it in another?
- What happens if you use bind() on an arrow function?
- Implement a polyfill for bind()
- When would you use apply() over call()?
- What will be the output?
- How does bind() help in asynchronous code like setTimeout?
- Can you rebind a function that’s already bound?
- Conclusion
- Further Reading and Resources

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:
this
in the Global Context
In non-strict mode:
console.log(this); // window (in browsers)
In strict mode:
'use strict';
console.log(this); // undefined
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()
.
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
(orglobal
) in non-strict modeundefined
in strict mode
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:
sdMethod | Executes Immediately | Accepts Arguments | Returns New Function |
call() | ✅ Yes | As individual args | ❌ No |
apply() | ✅ Yes | As an array | ❌ No |
bind() | ❌ No | As 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
orundefined
(will default to global object orundefined
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:
Feature | call() | apply() | bind() |
Execution | Immediately | Immediately | Returns a new function |
Arguments Format | Passed individually (arg1, arg2) | Passed as a single array ([arg1, arg2]) | Passed individually (arg1, arg2) |
Returns | Return value of the function | Return value of the function | A 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 Case | Directly invoking with custom this | Invoking with arguments in array form | Creating bound functions for later use |
Arrow Function Compatibility | Not 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:
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.
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.
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()
.
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.
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.
Fixing
this
InsidesetTimeout
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 modeundefined
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.
What is the difference between
call()
,apply()
andbind()
?
A common starter question to test knowledge of syntax and use cases.
Expected Answer:
call()
invokes the function with a specificthis
and arguments passed one by one.apply()
is likecall()
but takes arguments as an array.bind()
returns a new function with a boundthis
, without calling it immediately.\
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
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.
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])
}
}
When would you use
apply()
overcall()
?
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.
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
.
How does
bind()
help in asynchronous code likesetTimeout
?
Expected Answer:
In setTimeout
, the context (this
) is often lost. bind()
ensures that the original context is preserved when the function is executed later.
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 specifiedthis
and individual arguments.📦
apply()
— Just likecall()
, but arguments are passed as an array.🔗
bind()
— Returns a new function with a boundthis
, 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:
LeetCode JavaScript Problems
🎯 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.
Subscribe to my newsletter
Read articles from himanshu directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
