The this Keyword & Execution Binding
this in JavaScript is not like this in Java or self in Python. It's determined at call time, not definition time. This single difference causes more confusion than any other JavaScript concept â and it's why arrow functions exist.
The Four Binding Rules
this is resolved by checking these rules in order of precedence:
Rule 1: new Binding (Highest Priority)
function User(name) {
this.name = name;
}
const user = new User('Alice');
// this = the newly created object â { name: 'Alice' }Rule 2: Explicit Binding (call, apply, bind)
function greet() {
return `Hello, ${this.name}`;
}
const user = { name: 'Alice' };
greet.call(user); // "Hello, Alice"
greet.apply(user); // "Hello, Alice"
const bound = greet.bind(user);
bound(); // "Hello, Alice"| Method | Arguments | Returns | When applied |
|---|---|---|---|
call | (thisArg, arg1, arg2, ...) | Result of function | Immediately |
apply | (thisArg, [args]) | Result of function | Immediately |
bind | (thisArg, arg1, arg2, ...) | New bound function | When called later |
Rule 3: Implicit Binding
const user = {
name: 'Alice',
greet() {
return `Hello, ${this.name}`;
},
};
user.greet(); // "Hello, Alice" â this = user (the object before the dot)The calling object (what's before the dot) determines this. This is the most common binding but also the most fragile.
Rule 4: Default Binding (Lowest Priority)
function standalone() {
return this;
}
standalone(); // window (non-strict) or undefined (strict mode)When no other rule applies, this is the global object (or undefined in strict mode).
Why Implicit Binding Breaks
The #1 source of this bugs: extracting a method loses its context.
const user = {
name: 'Alice',
greet() {
return `Hello, ${this.name}`;
},
};
const greet = user.greet; // Extract the function
greet(); // "Hello, undefined" â implicit binding lost!
// Same problem with callbacks:
setTimeout(user.greet, 100); // "Hello, undefined"
button.addEventListener('click', user.greet); // this = button element
[1, 2].forEach(user.greet); // this = undefined (strict) or windowFix 1: bind
setTimeout(user.greet.bind(user), 100);Fix 2: Wrapper Arrow Function
setTimeout(() => user.greet(), 100);Fix 3: Arrow Function Method (React Pattern)
class Component {
state = { count: 0 };
handleClick = () => {
this.setState({ count: this.state.count + 1 });
};
}Arrow Functions: Lexical this
Arrow functions don't have their own this. They capture this from the enclosing lexical scope at definition time:
const team = {
name: 'Engineering',
members: ['Alice', 'Bob'],
listMembers() {
return this.members.map(member => {
return `${member} â ${this.name}`; // this = team (lexical, from listMembers)
});
},
};
team.listMembers(); // ["Alice â Engineering", "Bob â Engineering"]If we used a regular function in map:
listMembers() {
return this.members.map(function(member) {
return `${member} â ${this.name}`; // this = undefined (strict) or window!
});
}Arrow Functions Cannot Be Rebound
const arrow = () => this;
arrow.call({ name: 'ignored' }); // Still original this â call/apply/bind have no effect
new arrow(); // TypeError: arrow is not a constructorthis in Different Contexts
| Context | this is | Why |
|---|---|---|
| Global scope | window / globalThis | Default binding |
| Object method | The object | Implicit binding |
Constructor (new) | The new instance | New binding |
call / apply / bind | The specified object | Explicit binding |
| Arrow function | Enclosing scope's this | Lexical (no own this) |
| Event handler | The element | Browser sets it |
| Class method (extracted) | undefined (strict) | Lost implicit binding |
setTimeout callback | window (non-strict) | Default binding |
Practical Patterns
Method Chaining
class QueryBuilder {
#table = '';
#conditions = [];
#limit = null;
from(table) { this.#table = table; return this; }
where(condition) { this.#conditions.push(condition); return this; }
take(n) { this.#limit = n; return this; }
build() {
let sql = `SELECT * FROM ${this.#table}`;
if (this.#conditions.length) sql += ` WHERE ${this.#conditions.join(' AND ')}`;
if (this.#limit) sql += ` LIMIT ${this.#limit}`;
return sql;
}
}
new QueryBuilder()
.from('users')
.where('active = true')
.where('age > 18')
.take(10)
.build();Safe Context Extraction
function bindAll(obj, ...methods) {
methods.forEach(method => {
obj[method] = obj[method].bind(obj);
});
return obj;
}
const api = bindAll(new ApiClient(), 'get', 'post', 'delete');
// Now safe to pass api.get as a callback anywhereThe Interview Trick Questions
Question 1
const obj = {
name: 'outer',
inner: {
name: 'inner',
getName() { return this.name; },
},
};
obj.inner.getName(); // ?Answer: 'inner' â this is the object immediately before the dot (obj.inner).
Question 2
const getName = obj.inner.getName;
getName(); // ?Answer: undefined (strict mode) or '' / window.name (non-strict) â implicit binding lost.
Question 3
const obj = {
name: 'Alice',
greet: () => `Hello, ${this.name}`,
};
obj.greet(); // ?Answer: "Hello, undefined" â Arrow function has no own this; it uses the enclosing scope's this (module/global), not obj.
Interview Signal
Senior candidates demonstrate:
- The four rules in order â new > explicit > implicit > default
- Why implicit binding breaks â Extracting methods, passing as callbacks
- Arrow function behavior â Lexical
this, can't rebind, can't use as constructor - Practical fixes â bind, arrow functions, class fields
- No hesitation on trick questions â Predicting
thisin any context