Intermediate3 questionsFull Guide

JavaScript "this" Keyword Interview Questions

The "this" keyword is notoriously confusing and frequently tested. Master the 4 binding rules and arrow function behaviour.

The Mental Model

This is not determined by where a function is written — it's determined by how a function is called. That single sentence explains 90% of this confusion. Think of this as an invisible extra argument that every function receives. You don't pass it explicitly — JavaScript fills it in automatically based on the call site. The same function can receive a different this every time it's called, depending on how it was invoked. There are five ways to call a function, and each one fills in this differently: 1. As a method on an object: this = that object 2. As a plain function call: this = global (or undefined in strict mode) 3. With new: this = the newly created object 4. With call/apply/bind: this = whatever you explicitly pass 5. As an arrow function: no own this — inherits from the surrounding code Master these five rules and this becomes completely predictable.

The Explanation

Rule 1: Method call — this is the object to the left of the dot

const user = {
  name: 'Alice',
  greet() {
    console.log(`Hello, I'm ${this.name}`)
  }
}

user.greet()  // 'Hello, I'm Alice' — this = user (object left of dot)

The rule is about the call site, not the definition. The same function gets a different this depending on what object it's called on:

function greet() {
  console.log(`I'm ${this.name}`)
}

const alice = { name: 'Alice', greet }
const bob   = { name: 'Bob',   greet }

alice.greet()  // "I'm Alice" — this = alice
bob.greet()    // "I'm Bob"   — this = bob

Rule 2: Plain function call — this is global (or undefined in strict mode)

function whoAmI() {
  console.log(this)
}

whoAmI()  // window (browser) or global (Node.js)

// In strict mode:
'use strict'
function whoAmI() {
  console.log(this)  // undefined — strict mode prevents global binding
}
whoAmI()

The classic lost-context bug — extracting a method from an object and calling it as a plain function:

const user = {
  name: 'Alice',
  greet() { console.log(this.name) }
}

user.greet()         // 'Alice' ✓

const fn = user.greet
fn()                 // undefined (or TypeError in strict mode)
                     // fn() is a plain call — this is global, not user

Rule 3: Constructor call with new — this is the new object

function Person(name, age) {
  this.name = name   // this = newly created object
  this.age  = age
}

const alice = new Person('Alice', 30)
console.log(alice.name)  // 'Alice'
console.log(alice.age)   // 30

When you use new, JavaScript: (1) creates a new empty object, (2) sets this to that object, (3) runs the function body, (4) returns the object (unless the function returns a different object explicitly).

Rule 4: Explicit binding with call, apply, bind

function introduce(city, country) {
  console.log(`${this.name} from ${city}, ${country}`)
}

const alice = { name: 'Alice' }

// call — pass this, then individual args
introduce.call(alice, 'Mumbai', 'India')   // 'Alice from Mumbai, India'

// apply — pass this, then args as array
introduce.apply(alice, ['Mumbai', 'India']) // 'Alice from Mumbai, India'

// bind — returns a NEW function with this permanently locked
const aliceIntro = introduce.bind(alice)
aliceIntro('Mumbai', 'India')   // 'Alice from Mumbai, India'
aliceIntro('Delhi', 'India')    // 'Alice from Delhi, India'
// alice is permanently bound — calling differently has no effect

Rule 5: Arrow functions — no own this, inherits lexically

Arrow functions don't have their own this. They inherit this from the enclosing lexical context — the this of the surrounding code at the time the arrow function was written.

const timer = {
  count: 0,

  // Regular function — this would be lost in setTimeout callback
  startBad() {
    setTimeout(function() {
      this.count++  // this = window (plain callback), not timer
      console.log(this.count)  // NaN or error
    }, 1000)
  },

  // Arrow function — inherits this from startGood's execution context
  startGood() {
    setTimeout(() => {
      this.count++   // this = timer ✓ — arrow inherited it
      console.log(this.count)  // 1
    }, 1000)
  }
}

timer.startGood()

Arrow functions are the fix for "losing this in callbacks." The arrow function doesn't rebind this — it uses whatever this the surrounding function had.

class — this in method context

class Counter {
  count = 0

  increment() {
    this.count++
    console.log(this.count)
  }
}

const c = new Counter()
c.increment()  // 1 ✓ — method call, this = c

// The extraction bug still applies to classes:
const fn = c.increment
fn()  // TypeError in strict mode — this is undefined, not c

// Fix — bind in constructor:
class Counter2 {
  count = 0

  constructor() {
    this.increment = this.increment.bind(this)
  }

  increment() {
    this.count++
  }
}

// Fix — use arrow function as class field (most common in React):
class Counter3 {
  count = 0
  increment = () => {   // arrow field — this is permanently bound
    this.count++
  }
}

The precedence order

When multiple rules could apply, this is the priority:

  1. new binding — highest priority
  2. Explicit binding (call/apply/bind)
  3. Method call (implicit binding)
  4. Default binding (plain call) — lowest priority

Arrow functions are outside this hierarchy — they have no binding of their own and cannot be overridden by any of the above.

Common this bugs and fixes

// Bug 1: method passed as callback loses context
class Button {
  label = 'Click me'
  handleClick() {
    console.log(this.label)  // 'undefined' if called as callback
  }
}
const btn = new Button()
document.addEventListener('click', btn.handleClick)  // this = document element

// Fix: bind
document.addEventListener('click', btn.handleClick.bind(btn))

// Fix: arrow wrapper
document.addEventListener('click', () => btn.handleClick())

// Fix: arrow class field (React standard)
class Button2 {
  label = 'Click me'
  handleClick = () => {    // arrow — this permanently bound
    console.log(this.label)
  }
}

// Bug 2: chained method losing context
const { greet } = user    // destructuring extracts function
greet()                   // this = global, not user

// Fix: always call it as user.greet() or use bind

Common Misconceptions

⚠️

Many devs think this is determined by where a function is defined — but actually this is determined by how and where a function is called. A method extracted from an object and called as a plain function loses its this entirely, even though the function body didn't change.

⚠️

Many devs think arrow functions are just shorter syntax for regular functions — but actually the key semantic difference is that arrow functions don't have their own this binding. This makes them fundamentally different for event handlers, callbacks, and class methods where this matters.

⚠️

Many devs think bind permanently changes the function — but actually bind returns a new function with a fixed this. The original function is unchanged. You can bind the same function to different objects by calling bind multiple times on the original.

⚠️

Many devs think this in a nested function refers to the outer function's this — but actually a regular nested function gets its own this, defaulting to global (or undefined in strict mode). Only arrow functions inherit this from the enclosing scope. This is the source of the classic setTimeout and setInterval bugs.

⚠️

Many devs think new and call/apply/bind can override an arrow function's this — but actually arrow functions permanently use the this of their enclosing lexical context and cannot be overridden. Calling an arrow function with call(), apply(), or new doesn't change its this.

⚠️

Many devs think this in a class method always refers to the class instance — but actually this in a class method is whatever the call site makes it. Passing a class method as a callback (like onClick={this.handleClick}) detaches it from the instance, and this becomes undefined (in strict mode, which classes use automatically). That's why React developers use arrow class fields.

Where You'll See This in Real Code

React class components required this.handleClick = this.handleClick.bind(this) in constructors for years — the entire reason arrow class fields (handleClick = () => {}) became the preferred React pattern is to avoid this binding lost-context bug without cluttering the constructor.

Vue 3's Composition API largely eliminates this concerns — the Options API (Vue 2) required careful attention to this binding, which is one reason the Composition API uses plain function calls and closures instead of relying on this for component data access.

The addEventListener and removeEventListener pattern requires understanding this — if you pass a bound function to addEventListener, you must save the reference to remove it later, because .bind() creates a new function each time. Developers who don't know this leak event listeners.

async class methods and this — inside an async method, this works correctly as long as the method is called on the object. But if the async method is passed as a callback or used with Promise.then(), this binding is lost just like any other method, a common source of production bugs in service classes.

Node.js EventEmitter callbacks lose this — emitter.on('event', this.handler) detaches the handler from its class instance. The Node.js docs explicitly recommend using arrow functions or binding for this reason, but developers new to the event pattern regularly hit this bug.

TypeScript's noImplicitThis compiler option catches this binding bugs at compile time — it requires you to annotate what this should be in function signatures, and throws an error if TypeScript can't determine the type of this. Enabling it surfaces a class of bugs that would otherwise only appear at runtime.

Interview Cheat Sheet

  • Four rules (priority order): new > explicit (call/apply/bind) > implicit (object.method()) > default (global/undefined)
  • Arrow functions have NO own this — they inherit lexical this from where they are defined
  • Implicit binding is lost when you assign a method to a variable: const fn = obj.method; fn()
  • In strict mode, default this is undefined (not global)
  • bind() returns a new permanently-bound function; call() and apply() invoke immediately
💡

How to Answer in an Interview

  • 1.The "lost binding" trap (extracting a method) is asked at almost every interview
  • 2.For every code snippet: ask "How was this function CALLED?" — that determines this
  • 3.Arrow function this: ask "Where was this function DEFINED?" — look at enclosing scope
  • 4.Know call vs apply vs bind: call(thisArg, ...args), apply(thisArg, [args]), bind returns fn
📖 Deep Dive Articles
Arrow Functions vs Regular Functions in JavaScript: 6 Key Differences9 min readTop 50 JavaScript Interview Questions (With Deep Answers)18 min readJavaScript Prototypes Explained: The Object Model Behind Every Class and Method10 min readJavaScript `this` Keyword Explained: Five Rules, Zero Guessing10 min readJavaScript Output Questions: 30 Tricky Examples That Test Real Understanding14 min read

Practice Questions

3 questions
#01

How does 'this' work in different contexts?

🟢 Easy'this' Keyword PRO💡 Determined by how a function is called, not where it is defined
#02

this lost in event listener

🟡 MediumFix the CodeDebug
#03

this in nested function

🔴 Hard'this' Binding

Related Topics

JavaScript Prototype & Prototypal Inheritance Interview Questions
Advanced·6–10 Qs
JavaScript Closure Interview Questions
Intermediate·8–12 Qs
JavaScript Arrow Function Interview Questions
Intermediate·4–7 Qs
🎯

Can you answer these under pressure?

Reading answers is not the same as knowing them. Practice saying them out loud with AI feedback — that's what builds real interview confidence.

Practice Free →Try Output Quiz