The Event Loop
intermediateHow the call stack, microtask queue, and macrotask queue decide execution order — with the output-prediction questions interviewers love.
Master this-binding for interviews — default, implicit, explicit, and new binding, arrow functions, lost this in callbacks, and the precedence order.
this is not resolved when a function is defined — it's resolved when the function is called. The same function can have a different this on every invocation. Interviewers exploit exactly this gap: they show a method passed as a callback and ask what it logs. If you know the four binding rules and their precedence, every this question becomes mechanical.
A plain function call gets the global object as this (or undefined in strict mode):
function show() {
console.log(this);
}
show(); // window (browser, sloppy mode) — undefined in strict mode / modulesRemember: ES modules are always strict, so in any modern codebase the default binding is effectively undefined.
Call a function through an object, and this is that object — determined by the call site, not where the function lives:
const user = {
name: "Ada",
greet() {
return `Hi, ${this.name}`;
},
};
user.greet(); // "Hi, Ada" — called through userThe classic trap is losing the implicit binding by detaching the method:
const greet = user.greet;
greet(); // "Hi, undefined" — plain call, default binding
setTimeout(user.greet, 0); // same bug: the callback is a plain callThis "lost this" bug is the reason bind exists, and it's why interviewers ask you to implement call, apply, and bind from scratch.
call, apply, and bind set this directly:
function greet(punctuation) {
return `Hi, ${this.name}${punctuation}`;
}
const ada = { name: "Ada" };
greet.call(ada, "!"); // "Hi, Ada!" — arguments listed
greet.apply(ada, ["!"]); // "Hi, Ada!" — arguments as array
const bound = greet.bind(ada);
bound("?"); // "Hi, Ada?" — permanently bound copyTwo details that earn senior points:
bind-ed function cannot be re-bound — later call/apply can't change its this.null/undefined to call/apply falls back to default binding (globalThis in sloppy mode).Calling a function with new creates a fresh object, sets it as this, links its prototype, and returns it (unless the function returns its own object):
function User(name) {
this.name = name;
}
const u = new User("Ada"); // this = the newly created objectHow new interacts with the prototype chain is its own interview topic — see prototypal inheritance.
When rules collide, the order is:
new binding > explicit binding (bind/call/apply) > implicit binding > default binding.
function who() { console.log(this.name); }
const a = { name: "a", who };
const b = { name: "b" };
a.who(); // "a" implicit
a.who.call(b); // "b" explicit beats implicit
const boundA = who.bind(a);
boundA.call(b); // "a" bind can't be overridden by call
new (who.bind(a))(); // undefined new beats even bind (fresh object)Arrow functions don't have their own this — they close over the this of the enclosing scope, like any other variable (a closure over this):
const timer = {
seconds: 0,
start() {
setInterval(() => {
this.seconds++; // works: arrow inherits start()'s this
}, 1000);
},
};Replace the arrow with a regular function and this.seconds breaks — that's the single most common this interview snippet. Corollaries worth stating:
call/apply/bind have no effect on an arrow function's this.obj.method()), and as constructors (new throws).const obj = {
name: "outer",
regular() {
console.log("regular:", this.name);
const arrow = () => console.log("arrow:", this.name);
arrow();
function inner() {
console.log("inner:", this?.name);
}
inner();
},
};
obj.regular();
// regular: outer — implicit binding
// arrow: outer — lexical this from regular()
// inner: undefined — plain call, default bindingIf you can narrate that snippet rule-by-rule, you've passed the this portion of any interview.
this in a callback?" — arrow wrapper, bind, or capture the value you need before passing the function.this at the top level of a module?" — undefined in ESM; module.exports in CommonJS.this in class fields?" — class field arrows (handleClick = () => {}) bind per-instance; convenient for event handlers, but each instance gets its own function object.How the call stack, microtask queue, and macrotask queue decide execution order — with the output-prediction questions interviewers love.
What a closure really is, why loops with var trip people up, and how closures power memoization, once(), and module patterns.
The prototype chain, __proto__ vs prototype, and what class syntax actually does under the hood.