The Event Loop
intermediateHow the call stack, microtask queue, and macrotask queue decide execution order — with the output-prediction questions interviewers love.
How the prototype chain really works — __proto__ vs prototype, property lookup, what new and class do under the hood, and Object.create interview questions.
Every object has an internal link ([[Prototype]]) to another object. When you read a property that the object doesn't own, JavaScript walks up that chain of links until it finds the property or reaches null. That's the entire inheritance system — everything else (class, new, extends) is syntax and conventions layered on top.
const animal = {
eats: true,
describe() {
return `eats: ${this.eats}`;
},
};
const rabbit = Object.create(animal); // rabbit --[[Prototype]]--> animal
rabbit.hops = true;
rabbit.eats; // true — found one level up the chain
rabbit.describe(); // "eats: true" — this is rabbit (call-site rule)
rabbit.hasOwnProperty("eats"); // falseNote the this behavior: even though describe lives on animal, this is rabbit because of the call site — the implicit binding rule.
These are two different things with confusingly similar names:
__proto__ (spec: [[Prototype]], accessed properly with Object.getPrototypeOf) — the actual link an object uses for lookup. Every object has one.prototype — a plain property that only functions have. It's not the function's own prototype link; it's the object that will be assigned as [[Prototype]] of instances created by calling that function with new.function User(name) {
this.name = name;
}
const u = new User("Ada");
Object.getPrototypeOf(u) === User.prototype; // true
Object.getPrototypeOf(User) === Function.prototype; // true — different chain!A four-step answer that interviewers accept as complete:
function myNew(Constructor, ...args) {
// 1. Create a fresh object linked to Constructor.prototype
const obj = Object.create(Constructor.prototype);
// 2. Call the constructor with this = obj
const result = Constructor.apply(obj, args);
// 3. If the constructor returned an object, use it...
// 4. ...otherwise return the created object
return result !== null && typeof result === "object" ? result : obj;
}Implementing myNew is itself a common interview exercise, and it composes with custom apply and the new-edge-case in custom bind.
Reads walk the chain; writes don't. Assigning creates an own property that shadows the inherited one:
const defaults = { theme: "dark" };
const settings = Object.create(defaults);
settings.theme; // "dark" — inherited
settings.theme = "light";
settings.theme; // "light" — own property now shadows
defaults.theme; // "dark" — untouchedFollow-up trap: methods that mutate through an inherited reference do affect the shared object:
const base = { list: [] };
const child = Object.create(base);
child.list.push(1); // mutates base.list — no shadowing on mutation
base.list; // [1]This is exactly why shared mutable state on prototypes is an anti-pattern.
class Animal {
constructor(name) { this.name = name; }
speak() { return `${this.name} makes a sound`; }
}
class Dog extends Animal {
speak() { return `${this.name} barks`; }
}Under the hood:
Dog.prototype is an object whose [[Prototype]] is Animal.prototype (method inheritance).Dog itself links to Animal (static inheritance).super.speak() looks up the chain starting above Dog.prototype.new (classes throw without it).An interviewer who asks "what does class do that constructor functions don't?" wants those last three differences, not "it's just sugar."
Object.create(null) makes an object with no prototype at all — no inherited toString, no hasOwnProperty, and crucially no risk of prototype-key collisions:
const dict = Object.create(null);
dict["__proto__"] = "safe here"; // just a normal key
"toString" in dict; // falseMentioning this (and prototype-pollution attacks it defends against) is a strong senior signal. It also matters when implementing custom deep clone — do you preserve the prototype or not?
Object.hasOwn(obj, key) (ES2022) or hasOwnProperty; in checks the whole chain.Object.prototype, whose prototype is null.Object.getPrototypeOf in a loop comparing against Constructor.prototype.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 four binding rules, arrow-function behavior, and the lost-this bugs that call, apply, and bind exist to fix.