Every JavaScript object has a hidden internal link called [[Prototype]] pointing to another object. When you read obj.x, the engine checks obj itself, then follows that link, then the next, and so on until it finds x or reaches null. That chain of links is the prototype chain, and it is inheritance in JavaScript.
Watch the lookup
[[Prototype]] chain until it finds it — or hits null. Click a property to watch the lookup.Notice: bark is found one hop up (on Dog.prototype), greet two hops up, hasOwnProperty lives way up on Object.prototype, and fly is never found — so it returns undefined. No “class hierarchy” is consulted; the engine just walks links.
__proto__ vs prototype — the classic confusion
These are two different things with confusingly similar names:
prototypeis a property on constructor functions. It’s the object that will become the[[Prototype]]of instances the constructor creates. You never seeprototypeon a regular object — only on functions.__proto__(orObject.getPrototypeOf(obj)) is the actual[[Prototype]]link on an instance. It points at the constructor’sprototype.
function Dog(name) { this.name = name; }
Dog.prototype.bark = function () { return `${this.name}: woof`; };
const d = new Dog('Rex');
d.__proto__ === Dog.prototype; // true
Object.getPrototypeOf(d) === Dog.prototype; // true (preferred)
So new Dog() does roughly: create an empty object, set its [[Prototype]] to Dog.prototype, run the constructor with this bound to the new object, return it (unless the constructor explicitly returns an object).
A handy way to picture the three links per instance:
d.__proto__ === Dog.prototype
Dog.prototype.__proto__ === Object.prototype
Object.prototype.__proto__ === null // top of the chain
And one that trips people up: Dog.prototype.constructor === Dog. The prototype object has a back-pointer to its constructor, so d.constructor resolves (via the chain) to Dog.
Reads vs writes: the asymmetry
Property lookup walks the chain, but assignment does not. Writing obj.x = 1 always creates/updates an own property directly on obj — it never modifies a prototype. This is shadowing: the own property hides the inherited one for that object only.
console.log. Async logs (setTimeout/Promise) appear in real execution order.This is also why putting mutable data (like an array) on a prototype is a bug: all instances would share and mutate the one array, because reads find the shared one and push mutates it in place.
class is sugar
ES6 class does not introduce a new inheritance model. It’s a nicer syntax over exactly the chain above.
class Animal {
constructor(name) { this.name = name; }
greet() { return `Hi, I'm ${this.name}`; }
}
class Dog extends Animal {
bark() { return 'woof'; }
}
Here greet lives on Animal.prototype, bark on Dog.prototype, and Dog.prototype’s [[Prototype]] is Animal.prototype. extends just wires the chain; super() calls the parent constructor (and you must call it before using this in a subclass constructor). super.method() calls the parent’s version of a method. Everything the visualizer showed still applies.
A few class specifics interviewers probe:
- Methods are non-enumerable and live on the prototype (shared). Class fields (
x = 1) are own per-instance properties set in the constructor. - The whole class body runs in strict mode.
- Classes are not hoisted in a usable way — they sit in the TDZ like
let/const. extendscan extend any expression that resolves to a constructor (evennull).
hasOwnProperty & enumeration
Because lookup walks the chain, you often need to know if a property is the object’s own vs inherited:
console.log. Async logs (setTimeout/Promise) appear in real execution order.in checks the whole chain; hasOwnProperty checks only own properties; Object.keys/for...in differ too (for...in walks the chain for enumerable keys, Object.keys is own-only).
instanceof under the hood
x instanceof C does not compare types — it walks x’s prototype chain looking for C.prototype:
console.log. Async logs (setTimeout/Promise) appear in real execution order.Mixins
Since JS has single inheritance, shared behavior across unrelated classes is added via mixins — functions that copy methods onto a prototype:
const Serializable = {
toJSON() { return JSON.stringify(this); }
};
class User { constructor(n){ this.name = n; } }
Object.assign(User.prototype, Serializable); // mix in
new User('Ada').toJSON(); // '{"name":"Ada"}'
What’s the output? (puzzles)
Puzzle 1 — shared mutable prototype state:
console.log. Async logs (setTimeout/Promise) appear in real execution order.Answer: ['a']. Both instances read the same prototype array; push mutates it. Fix: initialize per-instance state in the constructor (this.items = []).
Puzzle 2 — shadowing then delete:
console.log. Async logs (setTimeout/Promise) appear in real execution order.Answer: 2 then 1. The own property shadows the prototype’s; deleting it un-hides the inherited value.
[[Prototype]] chain until found or null; class/extends is syntax that builds that chain, and methods live on the prototype (shared), while instance fields live on the object itself.this is dynamic, not lexical
A subtle trap: a method found on a prototype still runs with this bound to the object you called it on, not the object it was found on. Method lookup and this binding are separate steps.
console.log. Async logs (setTimeout/Promise) appear in real execution order.Object.create(null)
Object.create(proto) makes a new object whose [[Prototype]] is exactly proto. Passing null gives a “bare” dictionary with no prototype — no toString, no hasOwnProperty. People use it for safe key/value maps where you don’t want inherited keys colliding with user data (and to avoid prototype-pollution risks). Object.create is also the pre-class way to set up inheritance manually: Child.prototype = Object.create(Parent.prototype).
Common gotchas
__proto__vsprototype—prototypeis on constructors;__proto__/[[Prototype]]is the link on instances. UseObject.getPrototypeOf/setPrototypeOfover the legacy__proto__.- Mutable data on a prototype — shared across all instances; keep state in the constructor.
- Assignment doesn’t walk the chain —
obj.x = valways shadows with an own property; it never edits the prototype. for...inincludes inherited enumerable keys — guard withhasOwnPropertyor useObject.keys.- Forgetting
super()in a subclass constructor before touchingthisthrows. - Mutating
__proto__at runtime deoptimizes objects badly — set the prototype at creation instead. - Arrow functions have no
prototypeand can’t be used withnew.
Interview questions & model answers
Q: __proto__ vs prototype?
prototype is a property on constructor functions — the object instances will link to. __proto__ (a.k.a. [[Prototype]]) is the actual link on an instance, pointing at its constructor’s prototype.
Q: Is class a new inheritance model?
No — it’s syntax sugar over constructor functions and the prototype chain. Methods go on .prototype; extends/super wire the chain.
Q: How does property lookup work?
The engine checks own properties, then walks [[Prototype]] links upward until it finds the key or hits null (returning undefined).
Q: How does instanceof work?
It walks the object’s prototype chain checking whether Constructor.prototype appears in it — it’s structural, not type-based.
Q: What’s shadowing? An own property with the same name as an inherited one; the own property hides (shadows) the inherited value for that object only.
Q: Why Object.create(null)?
To get a prototype-less “pure dictionary” with no inherited keys like toString, avoiding collisions and prototype-pollution issues.
Q: How does method lookup interact with this?
Lookup finds the method on the prototype, but it runs with this bound to the receiver (the object before the dot) — lookup and binding are separate steps.
Say it out loud (30s)
“Objects link to a prototype via
[[Prototype]]. Property reads walk that chain until they find the key or hit null.prototypeis the property on a constructor that becomes the instances’ prototype;__proto__is the link itself.classandextendsare just sugar that builds this chain — methods sit on the prototype and are shared, andthisis bound at call time to the receiver, independent of where the method was found.”