When to Use JS bind vs call vs apply: Full Guide
A practical decision guide for choosing between JavaScript's bind, call, and apply. Learn the specific use cases where each method excels, their key differences, and how modern ES6+ features like arrow functions and spread syntax have changed the landscape.
JavaScript's call, apply, and bind all set this explicitly — but they serve different purposes. Knowing which one to reach for in any situation saves time and makes your intent clearer to other developers.
The Quick Decision Guide
Before diving in, here is the short version:
| Situation | Use |
|---|---|
Call a function once with a specific this | call |
| Call a function once with args already in an array | apply |
Create a reusable function with fixed this | bind |
| Store a method as a callback | bind |
| Borrow a method to use once | call |
| Spread an array as individual args (modern code) | fn(...array) (spread) |
Method needs permanent this for event handling | bind or class field arrow |
Use call When: Invoking Once With a Known this
call is best when you need to run a function once in a specific context and you already have the arguments as individual values:
// Method borrowing — use array method on array-like object
function logAll() {
const args = Array.prototype.slice.call(arguments);
console.log(args.join(" | "));
}
logAll("a", "b", "c"); // "a | b | c"
// Converting array-like to array (pre-ES6; prefer Array.from in modern code)
const nodeList = document.querySelectorAll("p");
const arr = Array.prototype.slice.call(nodeList);
// Type checking with Object.prototype.toString
function getType(val) {
return Object.prototype.toString.call(val).slice(8, -1);
}
getType([]); // "Array"
getType(new Map()); // "Map"call is also useful for invoking a constructor on an existing object or running a mixin function:
function withTimestamp() {
this.createdAt = new Date().toISOString();
}
function User(name, email) {
this.name = name;
this.email = email;
withTimestamp.call(this); // Mixin: add createdAt to User instance
}
const u = new User("Alice", "alice@example.com");
console.log(u.createdAt); // ISO timestampUse call in Class Constructor Inheritance (Pre-ES6)
Before super(), constructor inheritance was done with call:
function Animal(name) { this.name = name; }
function Dog(name, breed) {
Animal.call(this, name); // Initialize Animal props on this Dog instance
this.breed = breed;
}Modern code uses class/extends/super() for this — but you will see call in older codebases.
Use apply When: Invoking With args Already in an Array
apply shines when your arguments are already collected in an array and you need to spread them as individual arguments to a function:
// Dynamic argument count from an array
function buildUrl(protocol, host, port, path) {
return `${protocol}://${host}:${port}${path}`;
}
const parts = ["https", "api.example.com", 443, "/v2/users"];
buildUrl.apply(null, parts); // "https://api.example.com:443/v2/users"
// Math.max / Math.min with array of numbers
const prices = [12.99, 49.99, 7.50, 99.00, 24.99];
const lowest = Math.min.apply(null, prices); // 7.50
const highest = Math.max.apply(null, prices); // 99.00apply vs Spread Syntax
In ES6+, spread syntax often replaces apply:
// apply (works everywhere including older environments)
Math.max.apply(null, prices);
// Spread (ES6+, cleaner, same result)
Math.max(...prices);
// apply is still useful when you must pass a true array object (e.g., Function.prototype.apply itself)
// or when dealing with very large arrays where spread might hit argument length limitsThe main remaining reason to use apply over spread in modern code is when the argument array is dynamically constructed and you already have it as a variable — fn.apply(ctx, dynamicArgs) reads slightly clearer than fn.call(ctx, ...dynamicArgs) in some contexts.
Use bind When: Storing or Passing a Function as a Callback
bind is the right choice when the function will be called later, not immediately:
class VideoPlayer {
constructor(videoElement) {
this.video = videoElement;
this.isPlaying = false;
// These will be called later by event listeners — bind now
this.handlePlay = this.handlePlay.bind(this);
this.handlePause = this.handlePause.bind(this);
this.handleEnd = this.handleEnd.bind(this);
this.video.addEventListener("play", this.handlePlay);
this.video.addEventListener("pause", this.handlePause);
this.video.addEventListener("ended", this.handleEnd);
}
handlePlay() { this.isPlaying = true; console.log("Playing"); }
handlePause() { this.isPlaying = false; console.log("Paused"); }
handleEnd() { this.isPlaying = false; console.log("Ended"); }
destroy() {
// Need original references to removeEventListener
this.video.removeEventListener("play", this.handlePlay);
this.video.removeEventListener("pause", this.handlePause);
this.video.removeEventListener("ended", this.handleEnd);
}
}Binding in the constructor stores the bound reference, which is also needed to remove listeners later.
Use bind for Partial Application
bind pre-fills arguments to create specialized functions:
// API request function
async function apiRequest(method, endpoint, data) {
const response = await fetch(endpoint, {
method,
body: data ? JSON.stringify(data) : undefined,
headers: { "Content-Type": "application/json" },
});
return response.json();
}
// Partially applied variants
const get = apiRequest.bind(null, "GET");
const post = apiRequest.bind(null, "POST");
const put = apiRequest.bind(null, "PUT");
const del = apiRequest.bind(null, "DELETE");
// Clean call sites
get("/api/users");
post("/api/users", { name: "Alice" });
del("/api/users/42");bind vs Arrow Function: The Modern Trade-off
In many cases, a class field arrow function replaces bind:
// bind in constructor approach
class Component {
constructor() {
this.handleClick = this.handleClick.bind(this);
}
handleClick() { console.log(this); }
}
// Class field arrow approach (ES2022+)
class Component {
handleClick = () => { console.log(this); }; // Arrow — this is always the instance
}Key differences:
| bind in constructor | Class field arrow | |
|---|---|---|
| Location on instance | Own property (shadows prototype) | Own property |
| Memory | One function per bind call | One per instance (same) |
| Removable from event listener | Yes (stored reference) | Yes (stored as field) |
| Prototype method overridable | Effectively no | No |
| Works pre-ES2022 classes | Yes | No (needs class field support) |
For full context on arrow function this, see how arrow functions change this in JavaScript.
When None of Them Are Needed (Modern Defaults)
With ES6+ arrow functions and spread syntax, many classic use cases for call/apply/bind are now handled differently:
// OLD: apply to spread array as args
fn.apply(null, arr);
// NEW: spread syntax
fn(...arr);
// OLD: bind for callback this
setTimeout(this.update.bind(this), 100);
// NEW: arrow wrapper
setTimeout(() => this.update(), 100);
// OLD: Object.prototype.toString.call for type — still valid, no good modern alternative
Object.prototype.toString.call(value).slice(8, -1);bind, call, and apply remain essential for:
- Method borrowing from native prototypes
- Partial application
- Legacy and framework interop code
- Situations where an arrow wrapper would be verbose
Rune AI
Key Insights
- call for one-time method borrowing: When you want to run a function with a specific this once and already have discrete arguments, call is the cleanest choice
- apply for array-sourced arguments: When arguments exist as an array and you need to pass them to a function; modern code often prefers fn.call(ctx, ...array) with spread
- bind for deferred and repeated calls: When the function will be called later (event listeners, callbacks, stored references), bind permanently locks in this at assignment time
- bind for partial application: Pre-filling one or more arguments with bind creates specialized, reusable function variants — a functional programming staple
- Arrow functions and spread often replace these: In modern ES6+ code, arrow functions solve most callback this problems, and spread syntax replaces apply for array arguments — but call/bind remain essential for method borrowing and partial application
Frequently Asked Questions
Is there ever a reason to use apply over spread in modern code?
Can I bind and then call with a different this?
What about React? Should I use class field arrows or bind in the constructor?
Can call or apply pass arguments to a constructor?
Conclusion
call is for immediate invocation with individual arguments and a specific this — ideal for method borrowing. apply is for immediate invocation when your arguments are in an array — though spread syntax largely replaces it in modern code. bind is for creating a stored reference with a permanent this — essential for event callbacks and partial application. Understanding when each is appropriate — and when modern arrow functions or spread syntax make them unnecessary — is a mark of fluent JavaScript. See JS bind, call, and apply full tutorial for the full mechanics of each method.
More in this topic
OffscreenCanvas API in JS for UI Performance
Master the OffscreenCanvas API to offload rendering from the main thread. Covers worker-based 2D and WebGL rendering, animation loops inside workers, bitmap transfer, double buffering, chart rendering pipelines, image processing, and performance measurement strategies.
Advanced Web Workers for High Performance JS
Master Web Workers for truly parallel JavaScript execution. Covers dedicated and shared workers, structured cloning, transferable objects, SharedArrayBuffer with Atomics, worker pools, task scheduling, Comlink RPC patterns, module workers, and performance profiling strategies.
JavaScript Macros and Abstract Code Generation
Master JavaScript code generation techniques for compile-time and runtime metaprogramming. Covers AST manipulation, Babel plugin authorship, tagged template literals as macros, code generation pipelines, source-to-source transformation, compile-time evaluation, and safe eval alternatives.