Core Concepts10 min read · Updated 2025-06-01

JavaScript `this` Keyword Explained: Five Rules, Zero Guessing

`this` isn't random — it follows exactly five rules in a fixed priority order. Once you know them, every `this` question becomes a lookup, not a guess.

💡 Practice these concepts interactively with AI feedback

Start Practicing →

this has a reputation for being one of JavaScript's most confusing features. That reputation is earned — but the confusion comes from not having a model, not from genuine randomness.

this follows exactly five rules, applied in a specific priority order. Once you know the rules, every this question becomes a two-second lookup. This is the guide that makes that happen.

The Fundamental Principle

this is not determined by where a function is defined. It's determined by how a function is called.

This one sentence explains every this surprise. The same function can have a different this depending on how it's invoked. The source code of the function is irrelevant to what this is — only the call site matters.

(The one exception: arrow functions. More on that below.)

The Five Rules — In Priority Order

### Rule 1: new Binding (highest priority)

When a function is called with new, this is the newly created object:

function User(name) {
  this.name = name        // 'this' is the new object
  this.isActive = true
}

const alice = new User('Alice') alice.name // 'Alice' alice.isActive // true

new creates a new object, sets this to it, runs the constructor, and returns the new object. this inside the constructor is always that new object — nothing else can override it.

### Rule 2: Explicit Binding (call, apply, bind)

You can set this explicitly. Three methods, same result, different invocation style:

function introduce(greeting, punctuation) {
  return ${greeting}, I'm ${this.name}${punctuation}
}

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

// call — invokes immediately, arguments listed individually introduce.call(alice, 'Hello', '!') // "Hello, I'm Alice!" introduce.call(bob, 'Hey', '.') // "Hey, I'm Bob."

// apply — invokes immediately, arguments as array introduce.apply(alice, ['Hello', '!']) // "Hello, I'm Alice!"

// bind — returns a NEW function with 'this' permanently bound const aliceIntroduce = introduce.bind(alice) aliceIntroduce('Hi', '?') // "Hi, I'm Alice?" aliceIntroduce.call(bob, 'Hi', '?') // STILL "Hi, I'm Alice?" — bind wins over call

bind creates a new function with this locked in. Once bound, that this cannot be overridden — not by call, apply, or even new (well, new can override bind, but that's a niche case).

Partial application with bind:

function multiply(a, b) { return a * b }

const double = multiply.bind(null, 2) // 'this' doesn't matter here, null is fine double(5) // 10 double(10) // 20

### Rule 3: Implicit Binding (method calls)

When a function is called as a method — object.method()this is the object before the dot:

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

user.greet() // 'this' is user → "Hello, I'm Alice"

const nested = { name: 'outer', inner: { name: 'inner', greet() { return this.name // 'this' is the immediate object before the dot } } }

nested.inner.greet() // 'inner' — the object immediately before the dot

The implicit binding loss problem:

This is where most this bugs come from. When you extract a method from an object, you lose the implicit binding:

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

const greet = user.greet // extracted — no longer a method call greet() // 'this' is undefined (strict) or window (sloppy) // NOT user — the reference to user was lost

// Common real-world version: setTimeout(user.greet, 100) // same problem — passed as a function reference // Fix: setTimeout(() => user.greet(), 100) // arrow function preserves the call site setTimeout(user.greet.bind(user), 100) // explicit binding

### Rule 4: Default Binding (standalone function calls)

When none of the above apply — a function called on its own, not as a method — this defaults to:

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

showThis() // window (browser, sloppy mode)

function strictShowThis() { 'use strict' console.log(this) }

strictShowThis() // undefined

In modern code (ES modules are always strict, class bodies are always strict), default binding gives you undefined. Accidentally calling a method as a standalone function crashes loudly rather than silently using the global object — which is better.

### Rule 5: Arrow Functions — No this, Just Inheritance

Arrow functions are different. They don't have their own this binding at all. When an arrow function references this, it uses the this from the lexical scope where the arrow function was defined — not where it's called.

const user = {
  name: 'Alice',
  greet: function() {
    // Regular function — 'this' is user (implicit binding)
    const inner = () => {
      // Arrow function — inherits 'this' from greet's context
      return Hello, I'm ${this.name}
    }
    return inner()
  }
}

user.greet() // "Hello, I'm Alice"

// The classic use case: callbacks inside methods class Timer { constructor() { this.seconds = 0 }

start() { setInterval(() => { this.seconds++ // 'this' = Timer instance, from start()'s context console.log(this.seconds) }, 1000)

// If this were a regular function, 'this' inside setInterval would be // undefined (strict mode) — the Timer instance would be lost } }

Arrow functions capture this once, when they're created. Calling the arrow function differently, binding it with bind, calling it with call or apply — none of these can change its this. The this is baked in at definition time.

Where NOT to use arrow functions:

const obj = {
  name: 'Alice',

// ❌ Arrow function as object method — 'this' is NOT obj greet: () => { return this.name // 'this' is the enclosing scope (global/undefined) },

// ✓ Regular function as method — 'this' is the object greetFixed() { return this.name // 'Alice' } }

// ❌ Arrow functions can't be used as constructors const Foo = () => {} new Foo() // TypeError: Foo is not a constructor

// ❌ Arrow functions have no prototype property Foo.prototype // undefined — can't be used for prototype-based inheritance

Applying the Rules: A Decision Tree

When you see this and need to know what it refers to, ask these questions in order:

1. Is the function called with new?this is the new object 2. Is the function called with call, apply, or bind?this is the explicit argument 3. Is the function called as a method (obj.fn())?this is obj 4. Is this an arrow function?this is inherited from surrounding lexical scope 5. None of the above?this is undefined (strict) or global (sloppy)

The Interview Questions

"What logs here?"

const obj = {
  value: 42,
  getValue: function() {
    return this.value
  }
}

const fn = obj.getValue console.log(fn()) // undefined — implicit binding lost on extraction

"Fix this code so it logs 42:"

Three valid fixes:

// 1. Arrow function in the caller
const result = () => obj.getValue()
console.log(result())  // 42

// 2. bind const bound = obj.getValue.bind(obj) console.log(bound()) // 42

// 3. Call as a method console.log(obj.getValue()) // 42

"What does this log?"

function foo() {
  console.log(this.x)
}

const a = { x: 1, foo } const b = { x: 2, foo }

a.foo() // 1 — implicit, 'this' is a b.foo() // 2 — implicit, 'this' is b a.foo.call(b) // 2 — explicit overrides implicit

"Why does the arrow function work here but the regular function doesn't?"

class Component {
  constructor() {
    this.value = 10

// Arrow: captures 'this' (the instance) at construction time this.arrowMethod = () => this.value // always works

// Regular: 'this' depends on call site — breaks when passed as callback this.regularMethod = function() { return this.value } } }

const c = new Component() const { arrowMethod, regularMethod } = c

arrowMethod() // 10 — arrow's 'this' is permanently the instance regularMethod() // TypeError or undefined — 'this' lost on extraction

The One-Sentence Summary Per Rule

Five rules. Fixed priority. Once you've memorized them, this never surprises you again.

📚 Practice These Topics
This keyword
6–10 questions
Classes
5–8 questions
Prototype
6–10 questions
Closure
8–12 questions

Put This Into Practice

Reading articles is passive. JSPrep Pro makes you actively recall, predict output, and get AI feedback.

Start Free →Browse All Questions

Related Articles

Core Concepts
map() vs forEach() in JavaScript: Which One to Use and Why It Matters
7 min read
Core Concepts
Arrow Functions vs Regular Functions in JavaScript: 6 Key Differences
9 min read
Interview Prep
Promise.all vs allSettled vs race vs any: The Complete Comparison
9 min read