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.

JavaScriptintermediate
10 min read

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:

SituationUse
Call a function once with a specific thiscall
Call a function once with args already in an arrayapply
Create a reusable function with fixed thisbind
Store a method as a callbackbind
Borrow a method to use oncecall
Spread an array as individual args (modern code)fn(...array) (spread)
Method needs permanent this for event handlingbind 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:

javascriptjavascript
// 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:

javascriptjavascript
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 timestamp

Use call in Class Constructor Inheritance (Pre-ES6)

Before super(), constructor inheritance was done with call:

javascriptjavascript
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:

javascriptjavascript
// 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.00

apply vs Spread Syntax

In ES6+, spread syntax often replaces apply:

javascriptjavascript
// 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 limits

The 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:

javascriptjavascript
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:

javascriptjavascript
// 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:

javascriptjavascript
// 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 constructorClass field arrow
Location on instanceOwn property (shadows prototype)Own property
MemoryOne function per bind callOne per instance (same)
Removable from event listenerYes (stored reference)Yes (stored as field)
Prototype method overridableEffectively noNo
Works pre-ES2022 classesYesNo (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:

javascriptjavascript
// 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

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
RunePowered by Rune AI

Frequently Asked Questions

Is there ever a reason to use apply over spread in modern code?

Yes — when you're working with very large arrays. The spread operator and `call` use the JavaScript call stack for argument passing, which has a maximum limit (around 65k-500k arguments depending on the engine). `apply` also uses this limit, so for truly large arrays, a loop is better. That said, day-to-day code almost never hits this limit.

Can I bind and then call with a different this?

No. A bound function ignores any subsequent `this` argument from `call`, `apply`, or `bind`. The first `bind` wins permanently. Only `new` overrides `bind`.

What about React? Should I use class field arrows or bind in the constructor?

Modern React with hooks sidesteps this entirely. For class components, both work — the React docs historically showed the constructor bind pattern, but class field arrows are cleaner and equally valid.

Can call or apply pass arguments to a constructor?

Yes. `Constructor.call(existingObj, arg1, arg2)` runs the constructor body on `existingObj` (used for composition, not true construction). For actual object creation, `new` + `Reflect.construct` are the tools.

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.