AlgoInsight

Free lesson · JavaScript Basic to Advance

The `this` Keyword

Easy5 min read

The Most-Confused Keyword in JavaScript

The this keyword in JavaScript determines what value a function refers to as "the current object." Its value isn't fixed at function definition. It's determined at call time, based on how the function is called. This is the single most misunderstood part of the JavaScript language.

const user = {
  name: "Olena",
  greet: function() {
    return `Hi, I'm ${this.name}`;
  }
};

console.log(user.greet());            // "Hi, I'm Olena" — this is user

const detached = user.greet;
console.log(detached());              // "Hi, I'm undefined" — this is now something else

const otherUser = { name: "Nikhil", greet: user.greet };
console.log(otherUser.greet());       // "Hi, I'm Nikhil" — this is otherUser

Same function, three different values of this. The function itself doesn't change. What changes is how it's called. Understanding the rules that determine this is the goal of this Topic.

I'm Nikhil. I'm covering Topic 8.1 because this is fundamentally a runtime model question — same territory as Modules 3 and 6. The class material that follows (Topics 8.2 onward) goes to Brandon, who owns OO Design as his specialty area. The handoff is intentional: this is the foundation classes are built on, so we need it solid before classes make sense.

Interactive animation: Same function, two calls - the dot binds this to the object, while a bare call leaves it undefined.

The Four Rules for this

The value of this in a function call is determined by one of four rules. Knowing these rules cold is the path out of this confusion.

Rule 1: Method call. When a function is called as a property of an object — obj.method() — this is the object before the dot.

const user = {
  name: "Olena",
  greet() {
    return `Hi, ${this.name}`;
  }
};

console.log(user.greet());   // this is user → "Hi, Olena"

Rule 2: Function call. When a function is called as a standalone function — func() — this is undefined in strict mode (and the global object in non-strict mode).

function greet() {
  return `Hi, ${this?.name}`;
}

console.log(greet());   // this is undefined in strict mode

Rule 3: Constructor call. When a function is called with new — new Func() — this is the newly created object.

function User(name) {
  this.name = name;   // this is the new instance
}

const olena = new User("Olena");
console.log(olena.name);   // "Olena"

Rule 4: Explicit binding. When a function is called with call, apply, or bind — func.call(obj) — this is whatever you pass as the first argument.

function greet() {
  return `Hi, ${this.name}`;
}

const user = { name: "Olena" };
console.log(greet.call(user));   // this is user → "Hi, Olena"

These four rules cover almost every case. When in doubt about what this will be, ask "how is this function being called?" The answer to that question determines this.

graph TD
    A[How is the function called?] --> B[obj.method]
    A --> C[plain function]
    A --> D[new Function]
    A --> E[call/apply/bind]

    B --> F[this = obj]
    C --> G[this = undefined<br/>strict mode]
    D --> H[this = new instance]
    E --> I[this = first argument]

    style F fill:#7c3aed,color:#fff
    style H fill:#10b981,color:#fff
    style I fill:#3b82f6,color:#fff
    style G fill:#dc2626,color:#fff
This diagram shows the four rules that determine the value of this. When a function is called as a method on an object, this is the object before the dot. When it's called as a plain function with no object context, this is undefined in strict mode. When called with new as a constructor, this is the newly created instance. When called with call, apply, or bind, this is whatever you pass as the first argument.

Arrow Functions: The Exception

Arrow functions don't follow any of the four rules. Instead, they inherit this from their surrounding scope at the time they're defined. This is called lexical this.

const user = {
  name: "Olena",
  greetArrow: () => {
    return `Hi, ${this?.name}`;
  },
  greetMethod() {
    return `Hi, ${this.name}`;
  }
};

console.log(user.greetMethod());   // "Hi, Olena" — Rule 1 applies
console.log(user.greetArrow());    // "Hi, undefined" — arrow inherits from outer scope

Inside the arrow function greetArrow, this is not user even though we're calling user.greetArrow(). Arrow functions ignore the call rules entirely. They take whatever this exists in the scope where they're defined — in this case, the outer module scope, where this is undefined.

This is why we covered this rule in Topic 5.3: don't use arrow functions as object methods that need this. Use shorthand methods or regular function expressions for those cases.

But arrow functions are useful when you want to preserve this from an outer context — for example, in callbacks where regular functions would lose this:

const user = {
  name: "Olena",
  hobbies: ["coding", "reading", "hiking"],

  // Arrow function preserves 'this' from the method's context
  listHobbies() {
    return this.hobbies.map(hobby => `${this.name} enjoys ${hobby}`);
  }
};

console.log(user.listHobbies());

Inside listHobbies, this is user (because of Rule 1). The arrow function inside map inherits that same this. If we'd used a regular function in map, its this would be undefined because map calls callbacks as plain functions.

This is the most common legitimate use of arrow functions: preserving an outer this inside a nested callback. It's the trick that replaces the older const self = this pattern from pre-ES6 code.

Interactive animation: An arrow inside a method inherits that method's this, and call() on an arrow is silently ignored.

Losing this: The Detachment Problem

The biggest source of this bugs is detachment — when a method is stored or passed in a way that strips its object context.

const user = {
  name: "Olena",
  greet() {
    return `Hi, ${this?.name}`;
  }
};

// Direct call: 'this' is user
console.log(user.greet());

// Detached: stored in a variable
const greetFn = user.greet;
console.log(greetFn());   // "Hi, undefined" — no object context

// Passed to a callback: also detached
setTimeout(user.greet, 0);   // logs "Hi, undefined"

The function itself doesn't change. What changes is how it's called. user.greet() invokes Rule 1 (method call). greetFn() invokes Rule 2 (plain function call). The body of greet runs the same way in both cases, but this is different.

This is why callbacks lose their this. When you pass user.greet to setTimeout, you're passing the function value itself. setTimeout later calls it without any object context, so this is undefined.

The fix is explicit binding. The bind method creates a new function that's permanently bound to a specific this:

const user = {
  name: "Olena",
  greet() {
    return `Hi, ${this.name}`;
  }
};

const boundGreet = user.greet.bind(user);

console.log(boundGreet());   // "Hi, Olena" — bound to user

const detached = boundGreet;
console.log(detached());     // "Hi, Olena" — still bound

setTimeout(boundGreet, 0);   // works correctly

bind(user) returns a new function whose this is permanently fixed to user. No matter how you call the bound function — directly, as a callback, after detachment — this is always user.

Interactive animation: A method passed as a callback loses its dot - wrap the call in an arrow, or bind it once.

call, apply, and bind

Three methods on every function that let you control this:

function describe(adjective, intensity) {
  return `${this.name} is ${intensity} ${adjective}`;
}

const user = { name: "Olena" };

// call: arguments listed individually
console.log(describe.call(user, "talented", "very"));

// apply: arguments in an array
console.log(describe.apply(user, ["dedicated", "extremely"]));

// bind: returns a new function for later
const describeOlena = describe.bind(user);
console.log(describeOlena("focused", "remarkably"));

All three serve the same purpose — controlling this — but in different scenarios. call and apply invoke immediately. bind creates a reusable bound function. The difference between call and apply is purely how you pass arguments (individually vs. as an array).

In modern code with rest/spread syntax, you can use call for both cases:

function sum(a, b, c) {
  return a + b + c;
}

const args = [1, 2, 3];

console.log(sum.apply(null, args));        // old way
console.log(sum.call(null, ...args));       // modern way with spread
console.log(sum(...args));                  // even more modern

bind remains useful for creating bound versions of methods that will be called later — especially as event handlers and callbacks.

Common Pitfalls

Warning: The this traps.

Assuming this is determined by where a function is defined. It isn't — it's determined by how the function is called. Same function called four different ways produces four different this values.

Using arrow functions as object methods. Arrow functions ignore the call rules and use lexical this. For object methods that reference the object, use shorthand methods or function expressions.

Losing this by passing a method as a callback. setTimeout(obj.method, 1000) strips the this binding. Use bind, or wrap in an arrow function: setTimeout(() => obj.method(), 1000).

Forgetting that strict mode and non-strict mode behave differently. In strict mode (default in modules and classes), plain function calls produce this === undefined. In non-strict mode, they produce this === globalThis. Modern code is almost always strict.

Why This Matters in Interviews

Interview: this questions are foundational to senior JavaScript interviews. "Explain how this works in JavaScript" expects an answer covering the four rules and the arrow function exception. "What's the difference between call, apply, and bind?" is the standard follow-up.

Output-prediction puzzles involving this are extremely common. Code with methods, detachment, callbacks, and the question "what does this log?" — the candidate has to trace through which call rule applies at each step.

Topic 8.7 — Interview Lens: this, Classes, and OO Patterns — covers the deeper interview treatment, including the specific puzzles interviewers cycle through.

Try It

Exercise: Predict what each call logs, then run them all:

const user = {
  name: "Olena",
  greet() {
    return `Hi, ${this?.name}`;
  }
};

console.log(user.greet());                    // 1

const fn = user.greet;
console.log(fn());                            // 2

const other = { name: "Nikhil", greet: user.greet };
console.log(other.greet());                   // 3

console.log(user.greet.call({ name: "Emeka" }));   // 4

const bound = user.greet.bind(user);
const reBound = bound.bind({ name: "Hijacked" });
console.log(reBound());                       // 5

The fifth one is the trick: once a function is bound with bind, additional bind calls cannot rebind it. The first bind wins. Once you can predict all five answers without hesitation, you've mastered the four rules.

Back to JavaScript Basic to Advance