Understanding call, apply, and bind in JavaScript

In Brief
call, apply and bind all control the this value of a JavaScript function. call invokes the function with individual arguments, apply invokes it with an array‑like argument list and bind returns a new function with context or arguments fixed for later use.
JavaScript's this keyword still catches people out because it is decided by how a function is called, not where the function is written.
That distinction matters in real code reviews. You might see call, apply or bind in legacy modules, event handlers, callbacks, decorators, functional utilities, tests, mocks or third‑party library integrations. Modern JavaScript has reduced the need for manual binding in many day‑to‑day cases, but it has not removed the need to understand context.
The mistake to avoid is using bind as a quick patch without asking why the context was lost. Sometimes binding is exactly the right fix. Sometimes it hides a muddled component boundary, an extracted method being passed around too freely or a callback that should be an arrow function.
What call, apply and bind do
All three methods live on JavaScript functions. They let you control the this value explicitly.
The difference is when the function runs and how its arguments are supplied.
call
call invokes the function immediately. You pass the this value first, then each argument separately.
function greet(greeting, name) { console.log(`${greeting}, ${name}! My context is:`, this);}greet.call({ role: 'developer' }, 'Hello', 'Maddie');// Output: Hello, Maddie! My context is: { role: 'developer' }apply
apply also invokes the function immediately, but it expects the arguments as an array or array‑like object.
const numbers = [5, 10, 15];const max = Math.max.apply(null, numbers);console.log(max); // Output: 15bind
bind does not run the function straight away. It returns a new function with the chosen this value fixed for later.
const user = { name: 'Sophie' };function introduce() { console.log(`My name is ${this.name}`);}const boundIntroduce = introduce.bind(user);boundIntroduce(); // Output: My name is SophieThat makes bind useful when you need a function reference, not an immediate result.
call in practice
Use call when you want to run a function now and provide the context directly.
It is useful for borrowed methods, array‑like objects and small cases where explicit invocation is clearer than wrapping the function in another function.
Syntax of call
functionName.call(thisArg, arg1, arg2, ...);thisArg: The value to use asthisinside the function.arg1, arg2, ...: The arguments passed to the function one by one.
Borrowing methods with call
Borrowing a method is the classic example. The method belongs to one object or prototype, but you want to run it against another value.
const person = { firstName: 'John', lastName: 'Doe', fullName: function () { return `${this.firstName} ${this.lastName}`; },};const anotherPerson = { firstName: 'Jane', lastName: 'Smith' };// Borrowing fullName method for anotherPersonconsole.log(person.fullName.call(anotherPerson)); // Output: "Jane Smith"This pattern appears less often in modern application code than it used to, but it still appears around array‑like values, DOM APIs and old utility code.
Passing arguments with call
When the arguments are already known, call keeps the invocation direct.
function greet(greeting, punctuation) { console.log(`${greeting}, ${this.name}${punctuation}`);}const user = { name: 'Ellie' };// Invoking greet with a custom `this` and argumentsgreet.call(user, 'Hello', '!'); // Output: "Hello, Ellie!"What to watch with call
thisArgbecomesundefinedin strict mode when it isnullorundefined.- Outside strict mode, the fallback can be the global object, which is rarely what you want.
- If the argument list is already an array,
applyor spread syntax may be clearer.
apply in practice
apply is close to call, but the argument list is supplied as an array‑like value.
That makes it useful when a function receives or constructs arguments dynamically.
Syntax of apply
functionName.apply(thisArg, [arg1, arg2, ...]);thisArg: The value to use asthisinside the function.[arg1, arg2, ...]: An array or array‑like object containing the arguments.
Borrowing methods with apply
apply can borrow methods in the same way as call. The difference is the argument shape.
const numbers = [5, 10, 15, 20];const maxNumber = Math.max.apply(null, numbers);console.log(maxNumber); // Output: 20In this example, apply makes it easy to pass the array of numbers to Math.max.
Another apply example
function introduce(greeting, punctuation) { console.log(`${greeting}, my name is ${this.name}${punctuation}`);}const person = { name: 'Ellie' };const args = ['Hello', '!'];introduce.apply(person, args); // Output: "Hello, my name is Ellie!"What to watch with apply
- Use
applywhen the arguments are already array‑like. - For ordinary arrays, spread syntax is often easier to read in modern JavaScript.
- Do not use
applyjust to look clever. Explicit code usually wins in production reviews.
bind in practice
bind creates a new function with this fixed.
That is useful when a function will be called later by something else, such as an event listener, timer, callback or library API.
Syntax of bind
const boundFunction = functionName.bind(thisArg, arg1, arg2, ...);thisArg: The value to use asthisinside the new function.arg1, arg2, ...: Optional arguments that are pre‑set when the new function is called.
Preserving context with bind
const user = { name: 'Maddie', greet: function () { console.log(`Hello, ${this.name}!`); },};const greetUser = user.greet.bind(user);// Even if `greetUser` is called in a different context, `this` is preservedgreetUser(); // Output: "Hello, Maddie!"This is the use case most developers recognise: a method is passed around, and you still need it to run with the original object as this.
Pre‑setting arguments with bind
bind can also pre‑set arguments. That creates a specialised function from a more general one.
function multiply(a, b) { return a * b;}const double = multiply.bind(null, 2); // `a` is pre-set to 2console.log(double(5)); // Output: 10In this example, bind creates double by fixing the first argument of multiply to 2. Calling double(5) then calls multiply(2, 5).
What to watch with bind
- A bound function is a new function reference.
- Binding inside render paths or repeated setup code can create avoidable churn.
- If an arrow function would make the ownership clearer, prefer the clearer option.
- Do not use binding to hide a confused object model.
When to Use Each One
The short version is straightforward.
Use call for immediate invocation with listed arguments
const person = { name: 'John',};function introduce(age) { console.log(`Hi, I'm ${this.name} and I'm ${age} years old.`);}introduce.call(person, 30);// Output: Hi, I'm John and I'm 30 years old.call is useful when you want the invocation to be explicit and the argument list is known.
Use apply for immediate invocation with array‑like arguments
const numbers = [5, 1, 7, 3];const maxNumber = Math.max.apply(null, numbers);console.log(maxNumber); // Output: 7apply is useful when the arguments already exist as a list. In modern code, compare it with spread syntax before choosing it.
Use bind for delayed invocation
const printer = { prefix: 'Message:',};function printMessage(message) { console.log(`${this.prefix} ${message}`);}const boundPrinter = printMessage.bind(printer);boundPrinter('Hello, World!');// Output: Message: Hello, World!bind is useful when another part of the system will call the function later and you need the context or initial arguments to stay fixed.
The Production Caveat
Modern arrow functions and class fields mean you will often write less manual binding than older JavaScript articles suggest. That is a good thing.
The caveat is that production code still contains older patterns, library callbacks and framework integration points. Understanding call, apply and bind helps you read that code without guessing.
It also helps you avoid the lazy fix. If this changes unexpectedly, first ask how the function is being called. The related article on why `this` changes in JavaScript event handlers and methods covers that failure mode directly.
Use these methods when they make invocation clearer. Avoid them when they make the code look more advanced than the problem deserves.