The Prototype Chain & Inheritance

How property lookup walks [[Prototype]] up to null, and why class is just sugar over it.

must medium ⏱ 20 min prototypeinheritancethisclass
Mastery:
Why interviewers ask this
It's the bedrock of JS's object model. Interviewers use it to separate people who memorize `class` syntax from people who understand what actually happens at runtime. This is one of your flagged gaps.

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 explorer
When you read a property, JS walks up the [[Prototype]] chain until it finds it — or hits null. Click a property to watch the lookup.
Look up:

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:

  • prototype is a property on constructor functions. It’s the object that will become the [[Prototype]] of instances the constructor creates. You never see prototype on a regular object — only on functions.
  • __proto__ (or Object.getPrototypeOf(obj)) is the actual [[Prototype]] link on an instance. It points at the constructor’s prototype.
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.

Run it yourself
Edit and run. Output is captured from console.log. Async logs (setTimeout/Promise) appear in real execution order.
Console
 

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.
  • extends can extend any expression that resolves to a constructor (even null).

hasOwnProperty & enumeration

Because lookup walks the chain, you often need to know if a property is the object’s own vs inherited:

Run it yourself
Edit and run. Output is captured from console.log. Async logs (setTimeout/Promise) appear in real execution order.
Console
 

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:

Run it yourself
Edit and run. Output is captured from console.log. Async logs (setTimeout/Promise) appear in real execution order.
Console
 

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:

Run it yourself
Edit and run. Output is captured from console.log. Async logs (setTimeout/Promise) appear in real execution order.
Console
 

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:

Run it yourself
Edit and run. Output is captured from console.log. Async logs (setTimeout/Promise) appear in real execution order.
Console
 

Answer: 2 then 1. The own property shadows the prototype’s; deleting it un-hides the inherited value.

The one-sentence answer
Reading a property walks the [[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.

Run it yourself
Edit and run. Output is captured from console.log. Async logs (setTimeout/Promise) appear in real execution order.
Console
 

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__ vs prototypeprototype is on constructors; __proto__/[[Prototype]] is the link on instances. Use Object.getPrototypeOf/setPrototypeOf over the legacy __proto__.
  • Mutable data on a prototype — shared across all instances; keep state in the constructor.
  • Assignment doesn’t walk the chainobj.x = v always shadows with an own property; it never edits the prototype.
  • for...in includes inherited enumerable keys — guard with hasOwnProperty or use Object.keys.
  • Forgetting super() in a subclass constructor before touching this throws.
  • Mutating __proto__ at runtime deoptimizes objects badly — set the prototype at creation instead.
  • Arrow functions have no prototype and can’t be used with new.

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. prototype is the property on a constructor that becomes the instances’ prototype; __proto__ is the link itself. class and extends are just sugar that builds this chain — methods sit on the prototype and are shared, and this is bound at call time to the receiver, independent of where the method was found.”

Likely follow-up questions
  • What's the difference between __proto__ and prototype?
  • How does `class` relate to prototypes — is it a different mechanism?
  • What does `Object.create(null)` give you and why use it?
  • How does method lookup interact with `this`?
  • How does `instanceof` work under the hood?
  • What is property shadowing?

References