Skip to content

JavaScript 'this' Explained: call/apply/bind + Arrow Functions

A practical guide to 'this' in JavaScript: default binding, implicit binding, explicit binding (call/apply/bind), new binding, and how arrow functions change it.

Rajesh DhimanFebruary 08, 2026~18 min
On this page

30‑Second Senior Interview Answer

In JavaScript, this is not lexical in regular functions; it is determined strictly at runtime by how and where the function is called (its call-site).

The value of this is governed by a strict 4-tier precedence hierarchy:

  1. new Binding (highest priority): this points to the freshly instantiated object.
  2. Explicit & Hard Binding (call, apply, bind): this is explicitly specified by the caller.
  3. Implicit Binding: this is the object context immediately preceding the dot at the call-site (obj.method()).
  4. Default Binding (lowest priority): this resolves to undefined in strict mode ("use strict"), or globalThis (window in browsers) in non-strict mode.

Arrow functions are the sole exception: they do not define their own this binding. Instead, they capture this lexically from their enclosing lexical context at the time of creation, and their this value cannot be altered even by call, apply, or bind.


1. The Precedence Hierarchy (Memorize This Order)

When an interviewer shows a tricky code snippet, determine the value of this by asking these questions in strict order:

flowchart TD
    Q1{"Called with new?"}
    Q1 -- Yes --> A1["1. new Binding<br/>(Newly created object)"]
    Q1 -- No --> Q2{"Called with call/apply/bind?"}
    Q2 -- Yes --> A2["2. Explicit Binding<br/>(Specified context object)"]
    Q2 -- No --> Q3{"Called on a context object?<br/>(obj.fn())"}
    Q3 -- Yes --> A3["3. Implicit Binding<br/>(Context object preceding dot)"]
    Q3 -- No --> A4["4. Default Binding<br/>(undefined in strict, global in non-strict)"]

2. Deep Dive: The 4 Rules

Rule 1: Default Binding (Standalone Function Invocation)

When a function is called without any qualifying context:

function show() {
  console.log(this);
}
 
show(); // Window (non-strict) OR undefined (strict mode)

In ES modules and React components, strict mode is enforced automatically, meaning default binding always evaluates to undefined. Attempting to access properties like this.name will throw TypeError: Cannot read properties of undefined.


Rule 2: Implicit Binding (Object Property Call-Site)

When a function is invoked as a method of an object, implicit binding sets this to the object directly before the call dot:

const user = {
  name: 'Rajesh',
  greet() {
    console.log(`Hello, ${this.name}`);
  },
};
 
user.greet(); // "Hello, Rajesh" (call-site: user.greet)

The "Losing the Call-Site" Trap (Favorite Interview Question)

Implicit binding is fragile. Extracting a method reference or passing it as a callback immediately strips its object context:

const user = {
  name: 'Rajesh',
  greet() {
    console.log(this.name);
  },
};
 
// Trap 1: Variable assignment
const standaloneGreet = user.greet;
standaloneGreet(); // undefined (or TypeError in strict mode)
 
// Trap 2: Passed as callback to setTimeout or event listener
setTimeout(user.greet, 100); // undefined (callback executed via default binding)

Rule 3: Explicit Binding (call, apply, bind)

To forcefully set this regardless of the call-site, use Function.prototype methods:

function introduce(greeting, punctuation) {
  console.log(`${greeting}, I am ${this.name}${punctuation}`);
}
 
const developer = { name: 'Rajesh' };
 
// .call(thisArg, arg1, arg2, ...) -> Invokes immediately
introduce.call(developer, 'Hi', '!');
 
// .apply(thisArg, [argsArray]) -> Invokes immediately
introduce.apply(developer, ['Hey', '.']);
 
// .bind(thisArg, arg1, ...) -> Returns a new permanently bound function
const boundIntro = introduce.bind(developer, 'Greetings');
boundIntro('?');

Rule 4: new Binding

When a function is called with the new operator (as a constructor), JavaScript executes a precise 4-step algorithm:

  1. A brand new plain object ({}) is created in memory.
  2. The new object's internal prototype ([[Prototype]]) is linked to the constructor function's .prototype.
  3. The constructor function is executed with this bound to the newly created object.
  4. If the constructor explicitly returns a non-primitive object, that object is returned. Otherwise, the object created in Step 1 is returned.
function Engineer(name, role) {
  // 1. this = Object.create(Engineer.prototype)
  this.name = name;
  this.role = role;
  // 4. return this (implicit)
}
 
const eng = new Engineer('Rajesh', 'Staff Engineer');
console.log(eng.name); // "Rajesh"

[!WARNING] Constructor Return Override Trap: If a constructor explicitly returns an object (return { custom: true }), the newly created this instance is discarded. However, if it returns a primitive (return 42 or return "text"), the return value is ignored and this is returned.


3. Whiteboard Polyfill: Function.prototype.bind

Interviewers frequently ask candidates to write a production-grade polyfill for bind. A naive polyfill is trivial, but a senior candidate must handle currying (partial application) and the new operator override.

Function.prototype.myBind = function (context, ...boundArgs) {
  if (typeof this !== 'function') {
    throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable');
  }
 
  const targetFn = this;
 
  function boundFunction(...callArgs) {
    // If called with `new boundFunction()`, `this` is an instance of boundFunction.
    // In that case, use the newly created `this` instead of the original `context`!
    const isNew = this instanceof boundFunction;
    const finalContext = isNew ? this : context;
 
    return targetFn.apply(finalContext, [...boundArgs, ...callArgs]);
  }
 
  // Preserve prototype chain for instances created via `new`
  if (this.prototype) {
    boundFunction.prototype = Object.create(this.prototype);
  }
 
  return boundFunction;
};

Whiteboard Polyfill: Function.prototype.call

Using modern JavaScript symbols to avoid property collisions on the context object:

Function.prototype.myCall = function (context, ...args) {
  // If context is null or undefined, default to globalThis
  const targetContext = context ?? globalThis;
 
  // Use Symbol to avoid overwriting existing properties
  const fnKey = Symbol('fn');
 
  // Box primitives into objects if needed (e.g. Object(targetContext))
  const boxedContext = Object(targetContext);
  boxedContext[fnKey] = this;
 
  try {
    return boxedContext[fnKey](...args);
  } finally {
    delete boxedContext[fnKey];
  }
};

4. Arrow Functions: The Lexical Exception

Arrow functions do not have their own this. They do not possess an internal [[Construct]] method (cannot be used with new) and do not create an execution context this binding.

const company = {
  name: 'Eunix Tech',
  employees: ['Rajesh', 'Amit'],
  printRoster() {
    // Regular function here: `this` === company
    this.employees.forEach((emp) => {
      // Arrow function captures `this` from printRoster lexical scope:
      console.log(`${emp} works at ${this.name}`);
    });
  },
};
 
company.printRoster();
// "Rajesh works at Eunix Tech"
// "Amit works at Eunix Tech"

Arrow Functions Ignore Explicit Binding

Because arrow functions do not have a this binding, call, apply, and bind cannot alter their this:

const getArrowThis = () => this;
const customContext = { name: 'Eunix' };
 
console.log(getArrowThis.call(customContext) === customContext); // ❌ FALSE

5. Senior Interview Edge Cases & Puzzles

Puzzle 1: Object Literal is NOT a Scope!

const profile = {
  name: 'Rajesh',
  getName: () => {
    return this.name;
  },
};
 
console.log(profile.getName());

Answer: undefined.
Why? Curly braces { ... } in an object literal create an object, not a block or functional scope. The arrow function captures this from the enclosing scope where profile was defined (the module or window), where this.name is undefined.


Puzzle 2: new vs .bind() — Who Wins?

function Point(x, y) {
  this.x = x;
  this.y = y;
}
 
const fakeContext = { x: 99, y: 99 };
const BoundPoint = Point.bind(fakeContext);
 
const pt = new BoundPoint(1, 2);
console.log(pt.x, pt.y);
console.log(fakeContext.x, fakeContext.y);

Answer:
pt.x = 1, pt.y = 2
fakeContext.x = 99, fakeContext.y = 99

Why? According to the specification, the new operator overrides hard binding. When BoundPoint is instantiated with new, the bound context fakeContext is ignored, and a new instance of Point is created.


Puzzle 3: Nested Method Execution Trace

What does this print?

const calculator = {
  multiplier: 2,
  compute(arr) {
    return arr.map(function (n) {
      return n * this.multiplier;
    });
  },
};
 
console.log(calculator.compute([1, 2, 3]));

Answer: [NaN, NaN, NaN] (or TypeError in strict mode).
Why? The anonymous function passed inside map is invoked as a standard callback (default binding). Inside that callback, this is window (or undefined), meaning this.multiplier is undefined. 1 * undefined === NaN.

How to fix it:

  1. Pass arrow function: arr.map(n => n * this.multiplier)
  2. Pass thisArg to map: arr.map(function(n) { ... }, this)
  3. Pre-bind: arr.map(fn.bind(this))

Summary Checklist

  • I know the 4 rules in order: new > Explicit > Implicit > Default.
  • In strict mode, default binding is undefined, not window.
  • Detaching a method (const f = obj.method) drops implicit binding.
  • Arrow functions capture this lexically and ignore call/apply/bind.
  • Object literal braces {} do NOT create a lexical this scope.
  • I can write the whiteboard polyfill for bind including partial application and new handling.

Written by Rajesh Dhiman at Eunix Tech.

Companion Book

Looking for structured, offline interview prep?

Free online lessons • Portable Amazon Kindle / Paperback

JavaScript Interview Guide cover
Recommended for Core JS Rounds

JavaScript Interview Guide

By Rajesh Dhiman

Deep dive into the Event Loop, microtask draining, closures, prototypal inheritance, and asynchronous execution patterns.

View on Amazon ↗Kindle & Paperback
Frontend Interview Guide (2025 Edition) cover
Recommended for React & Frontend Rounds

Frontend Interview Guide (2025 Edition)

By Rajesh Dhiman

Comprehensive preparation covering React internals, Web Performance, CSS architecture, and system design questions.

View on Amazon ↗Kindle & Paperback

Note: All lessons on Frontend Interview remain completely free to read online. As an Amazon Associate, we may earn from qualifying purchases.

RD

Written by Rajesh Dhiman

Authored by Rajesh Dhiman and engineered at Eunix Tech. Focused on deep-dive frontend engineering, JavaScript runtime internals, React architecture, and web performance.