JavaScriptMedium

Ways to bind 'this' in JavaScript

01

The Short Answer

JavaScript provides four ways to control what this refers to: bind() creates a new function with this permanently set, call() invokes a function immediately with a specified this, apply() does the same but takes arguments as an array, and arrow functions lexically inherit this from their enclosing scope. Understanding these is essential because this in JavaScript is determined by how a function is called, not where it's defined — and these methods let you override that behavior.

02

bind() — Create a Bound Function

bind() returns a new function with this permanently locked to the value you provide. It doesn't call the function — it creates a new version of it. This is useful when you need to pass a method as a callback but want to preserve its this context. Once bound, the this value cannot be changed, even with call or apply.

bind.tstypescript
const user = {
  name: 'Alice',
  greet() {
    return `Hello, I'm ${this.name}`;
  },
};

// Problem: passing method as callback loses `this`
const greetFn = user.greet;
greetFn(); // "Hello, I'm undefined" — `this` is now global/undefined

// Solution: bind locks `this` permanently
const boundGreet = user.greet.bind(user);
boundGreet(); // "Hello, I'm Alice" ✅

// bind can also pre-fill arguments (partial application)
function multiply(a: number, b: number) {
  return a * b;
}
const double = multiply.bind(null, 2); // `this` = null, first arg = 2
double(5); // 10
double(3); // 6
03

call() and apply() — Invoke Immediately

Unlike bind(), both call() and apply() invoke the function immediately with the specified this value. The only difference between them is how they accept arguments: call() takes them individually (comma-separated), while apply() takes them as an array. A helpful mnemonic: Call = Commas, Apply = Array.

call-apply.tstypescript
function introduce(greeting: string, punctuation: string) {
  return `${greeting}, I'm ${this.name}${punctuation}`;
}

const person = { name: 'Alice' };

// call — arguments passed individually
introduce.call(person, 'Hi', '!');   // "Hi, I'm Alice!"

// apply — arguments passed as array
introduce.apply(person, ['Hey', '.']); // "Hey, I'm Alice."

// Real-world use: borrowing methods
const arrayLike = { 0: 'a', 1: 'b', 2: 'c', length: 3 };
const realArray = Array.prototype.slice.call(arrayLike); // ['a', 'b', 'c']

// Finding max in array (before spread existed)
const numbers = [5, 2, 8, 1];
Math.max.apply(null, numbers); // 8
// Modern equivalent: Math.max(...numbers)

In modern JavaScript, apply is less common because the spread operator handles the array-to-arguments conversion more cleanly. However, call is still useful for method borrowing — calling a method from one object's prototype on a different object.

04

Arrow Functions — Lexical this

Arrow functions don't have their own this — they capture this from the surrounding scope at the time they're defined. This makes them immune to the 'lost this' problem that plagues regular functions when used as callbacks. You can't override an arrow function's this with bind, call, or apply — it's permanently locked to the lexical scope.

arrow-this.tstypescript
class Timer {
  seconds = 0;

  // ❌ Regular function — `this` is lost in setTimeout callback
  startBroken() {
    setInterval(function () {
      this.seconds++; // `this` is the global object, not Timer
    }, 1000);
  }

  // ✅ Arrow function — `this` is inherited from startFixed()
  startFixed() {
    setInterval(() => {
      this.seconds++; // `this` is the Timer instance ✅
    }, 1000);
  }
}

// Arrow functions can't be rebound
const obj = { name: 'Alice' };
const arrow = () => this; // `this` is whatever it was when defined
arrow.call(obj);  // Still the outer `this`, NOT obj
arrow.bind(obj)(); // Still the outer `this`, NOT obj
05

Comparison

MethodInvokes immediately?Arguments formatCan rebind later?Use case
bind()No — returns new functionIndividual (partial application)No — permanently boundEvent handlers, callbacks
call()YesIndividual (comma-separated)N/A — already calledMethod borrowing, immediate invocation
apply()YesArrayN/A — already calledPassing arrays as arguments (legacy)
Arrow functionN/A — defines functionN/ANo — lexically lockedCallbacks, class methods, closures
06

Modern Best Practices

In modern codebases, arrow functions handle most this-binding needs automatically. You rarely need explicit bind, call, or apply anymore. Here's the decision tree for choosing the right approach in today's JavaScript.

Prefer these approaches

  • Arrow functions for callbacks and class methods — automatic lexical this
  • bind() when you must pass a regular function as a callback and preserve this
  • call() for method borrowing (Array.prototype.slice.call on array-likes)

Avoid these

  • apply() for spreading arrays — use spread operator instead
  • bind() in render methods — creates new function every render (use arrow class fields)
  • Relying on implicit this binding — always be explicit about what this refers to
07

Why Interviewers Ask This

This question tests your understanding of one of JavaScript's most confusing features. Interviewers want to see that you know the difference between bind (returns function) vs call/apply (invokes immediately), understand why arrow functions solve most this-binding problems, can identify when this gets lost (callbacks, event handlers, setTimeout), and know the modern best practices. It's a fundamental JavaScript question that separates developers who understand the language from those who just use frameworks.

Quick Revision Cheat Sheet

bind(): Returns new function with this locked — doesn't invoke

call(): Invokes immediately — args as commas

apply(): Invokes immediately — args as array

Arrow functions: Inherit this from enclosing scope — can't be rebound

Mnemonic: Call = Commas, Apply = Array, Bind = returns Bound function

Modern default: Use arrow functions — they handle this automatically