ToolPopToolPop
JavaScript · Lesson 11 of 18

this, call/apply/bind, and the arrow-function trap

7 min readUpdated 24 Jun 2026

this is the most context-dependent word in JavaScript. Move the same function from one object to another and this changes. Wrap it in an arrow and this freezes. Pass it to setTimeout and this evaporates.

Most developers learn this by trial and error. Seniors learn the four binding rules and then never get surprised again. That is what this lesson does.

The one rule that beats every other rule

this is decided by how a function is called, not where it was written. Same function, different call site, different this. Hold that thought.

There are four call patterns. JS checks them in this order.

  1. new binding. new Foo() creates a new object and binds this to it.
  2. Explicit binding. fn.call(obj), fn.apply(obj), or fn.bind(obj) set this to obj.
  3. Implicit binding. obj.fn() sets this to obj.
  4. Default binding. Plain fn() sets this to globalThis in sloppy mode, or undefined in strict mode and ES modules.
Diagram
rendering diagram...
How JS decides what this is at the call site

The implicit binding trap

js
const rider = {
  name: "Ankit",
  greet() {
    console.log("Hi", this.name);
  },
};
 
rider.greet();              // Hi Ankit
const grab = rider.greet;
grab();                     // Hi undefined (strict) or Hi '' (sloppy)

Step-by-step.

  1. Step 1. rider.greet() is the implicit pattern. this becomes rider. Prints "Hi Ankit".
  2. Step 2. const grab = rider.greet copies the function reference, not the binding. Now grab is just a plain function.
  3. Step 3. grab() is the default pattern. this is undefined in strict mode. this.name throws or prints undefined.

Interview trap: "this is lost" almost always means the call site changed. Look at the dot, or the absence of it, right before the parentheses.

Arrow functions, the lexical this

Arrow functions do not have their own this. They reach up to the surrounding lexical scope and use that one. Permanently. You cannot override it with call, apply, bind, or even new.

This is the single biggest difference between function and =>. It is not about syntax. It is about identity.

js
const order = {
  items: ["dosa", "filter coffee"],
  total: 0,
  charge() {
    this.items.forEach(function (item) {
      this.total += 100;     // this is undefined, breaks
    });
  },
};

The regular function passed to forEach gets its own this, which defaults to undefined. Swap it for an arrow.

js
charge() {
  this.items.forEach((item) => {
    this.total += 100;       // arrow inherits this from charge, which is order
  });
}

Now this is order. The arrow did not get its own. It borrowed charge's.

Interview trap: never use an arrow function as an object method. const obj = { greet: () => this.name } does NOT bind this to obj. It binds to the module or global scope.

call, apply, bind

Three ways to set this manually. Same job, different ergonomics.

js
function transfer(amount, currency) {
  console.log(`${this.name} sent ${currency}${amount}`);
}
 
const user = { name: "Priya" };
 
transfer.call(user, 500, "INR ");      // pass args one by one
transfer.apply(user, [500, "INR "]);   // pass args as array
const send = transfer.bind(user, 500); // returns new function, no call yet
send("INR ");                          // INR 500
  • call(thisArg, a, b). Invokes immediately with positional args.
  • apply(thisArg, [a, b]). Invokes immediately with an array of args. Useful when args are already in an array.
  • bind(thisArg, a). Returns a new function with this and optional args permanently set. Does not invoke.

Mnemonic: C for Comma, A for Array, B for Bind-and-wait.

js
const upi = {
  bank: "HDFC",
  send(amount) {
    return `${this.bank} debit ₹${amount}`;
  },
};
 
const debitFromICICI = upi.send.bind({ bank: "ICICI" });
debitFromICICI(1499); // "ICICI debit ₹1499"

bind is how React class components used to fix the "this in handler" problem before arrow methods became standard.

Quick reference

Implicit binding
this is set by the object on the left of the dot at call time.
Explicit binding
this is set by call, apply, or bind.
Lexical this
Arrow functions do not get their own this. They use the enclosing scope's this.
bind
Returns a new function with this and optionally some arguments pre-set.

Predict the output

js
const counter = {
  n: 0,
  tickRegular: function () { setTimeout(function () { this.n++; }, 0); },
  tickArrow:   function () { setTimeout(() => { this.n++; }, 0); },
};
counter.tickRegular();
counter.tickArrow();
console.log(counter.n);

Step by step.

  1. Step 1. tickRegular schedules a regular function. When it fires, this is undefined (strict) and the n++ throws or mutates the wrong object. counter.n stays 0.
  2. Step 2. tickArrow schedules an arrow. It inherits this from tickArrow, which was called as counter.tickArrow(). So this is counter, and counter.n becomes 1.
  3. Step 3. console.log(counter.n) runs synchronously, before either timer fires. Prints 0. After the timers run, counter.n is 1.

Senior rule: arrow functions are not "shorter functions". They permanently inherit this from their birth scope. Use them for callbacks where you want to keep outer this. Never use them as methods or constructors.

Quick quiz, prove you got it

0/3 answered
  1. 1.In `const fn = obj.method; fn();` (non-strict), what is `this` inside the call?
  2. 2.What does `func.bind(obj)` return?
  3. 3.Inside an arrow function, what is `this`?

Interview insights

When asked "explain this in JS"

  • this depends on HOW the function is called, not where it is defined
  • The four binding rules: default, implicit, explicit (call/apply/bind), new
  • Arrow functions are the exception: lexical this
  • A real trap example, like assigning a method to a variable and losing this

Tricky questions they will ask

Q.What does `arr.forEach(function() { console.log(this); })` print?
common wrong answer:The array itself

A.In non-strict mode, the global object. In strict mode, undefined. forEach does not bind this for the callback unless you pass a thisArg as the second argument.

Q.Can you bind this on an arrow function?
common wrong answer:Yes, like any other function

A.No. bind/call/apply have no effect on arrow functions because arrows do not have their own this in the first place. The lexical this wins.

Free tools you can use while you learn

Common questions

Q.What is the difference between call, apply, and bind?
A.call and apply both invoke the function immediately with a specified this. call takes arguments comma-separated, apply takes them as an array. bind returns a new function with this permanently set; it does not invoke.
Q.Why do arrow functions not have their own this?
A.They were designed that way. Arrow functions inherit this from the enclosing lexical scope at definition time. This is exactly what you want inside callbacks like setTimeout or array methods.
Chai0/2 done

Watching quietly. Tap me if you want a tip.

JS Playground
console
// hit run to see output

Try this (0 of 2 done)

  1. 1

    Use .bind to fix the lost this, so it logs "Hello, Aarav".

    show answer
    const customer = { name: 'Aarav', greet() { console.log('Hello, ' + this.name); } };
    const greet = customer.greet.bind(customer);
    greet();
  2. 2

    Use .call to greet on behalf of a different customer named "Ishita".

    show answer
    const customer = { name: 'Aarav', greet() { console.log('Hello, ' + this.name); } };
    customer.greet.call({ name: 'Ishita' });