Prototype Chain
โฑ๏ธ ~4-minute bite ยท solve the sandbox to master
5-Year-Old Metaphor
โ The physical, real-world picture. No jargon.๐ Every object has a secret pointer โ [[Prototype]] โ to its parent blueprint. Property lookup climbs this chain until it finds the property or hits null.
The blueprint analogy
Imagine a contractor who doesn't have every tool in their toolbox โ they have a toolbox (own properties) plus a secret pointer to a shared workshop (the prototype). If they don't have a tool themselves, they walk next door to the workshop. If the workshop doesn't have it, the workshop has its own pointer to an even bigger supply depot. This chain continues until someone has the tool or you reach a location with no next pointer (null).
JavaScript stores this pointer in an internal slot called [[Prototype]]. Every object has exactly one โ either pointing to another object or to null (end of chain).
Lookup algorithm โ ES spec ยง10.1.8.1 OrdinaryGet
| 1 | // Pseudocode of what the engine does when you access obj.prop |
| 2 | function OrdinaryGet(obj, prop) { |
| 3 | // 1. Check own properties first |
| 4 | const ownDesc = Object.getOwnPropertyDescriptor(obj, prop); |
| 5 | if (ownDesc !== undefined) return ownDesc.value; |
| 6 | ย |
| 7 | // 2. Follow [[Prototype]] |
| 8 | const parent = Object.getPrototypeOf(obj); |
| 9 | if (parent === null) return undefined; // end of chain |
| 10 | ย |
| 11 | // 3. Recurse up the chain |
| 12 | return OrdinaryGet(parent, prop); |
| 13 | } |
This recursive walk happens every time you access a property via dot or bracket notation. The engine checks own properties first, then climbs one link at a time.
Three confusing accessors โ memorize the difference
obj.__proto__
Legacy instance accessor โ reads/writes [[Prototype]] of obj. Works in browsers but not in null-proto objects. Deprecated. Never use in production.
Object.getPrototypeOf(obj)
The standard, spec-compliant way to read [[Prototype]]. Works on all objects including null-proto ones. Always prefer this.
Foo.prototype
Completely different thing! This is the object that new Foo() instances will have as their [[Prototype]]. It lives on the function object, not on instances. Confusing same word, different concept.
| 1 | function Dog() {} |
| 2 | const d = new Dog(); |
| 3 | // d's [[Prototype]] === Dog.prototype โ (instance's parent) |
| 4 | // Dog's [[Prototype]] === Function.prototype (Dog is a function) |
class syntax is prototype sugar
class syntax
| 1 | class Animal { |
| 2 | speak() { return "..."; } |
| 3 | } |
| 4 | class Dog extends Animal { |
| 5 | bark() { return "Woof!"; } |
| 6 | } |
| 7 | const d = new Dog(); |
desugared prototype assignments
| 1 | // Engine does exactly this: |
| 2 | Animal.prototype.speak = function() { |
| 3 | return "..."; |
| 4 | }; |
| 5 | Dog.prototype = Object.create( |
| 6 | Animal.prototype |
| 7 | ); |
| 8 | Dog.prototype.constructor = Dog; |
| 9 | Dog.prototype.bark = function() { |
| 10 | return "Woof!"; |
| 11 | }; |
class introduces no new mechanism. It is syntactic sugar over prototype chain assignment โ the same chain that exists for arrays, functions, and plain objects.
Interactive Sandbox
โ Move something, see it react instantly.Object type
Code
| 1 | const obj = { a: 1, b: 2 }; |
| 2 | obj.toString(); // found at Object.prototype |
[[Prototype]] chain
Your object โ holds own properties only
โณ no own ยซtoStringยป โ follow [[Prototype]]
Every plain object's prototype โ the root of most chains
โณ ยซtoStringยป found here โ
End of chain โ nothing above Object.prototype
Lookup trace โ ยซtoStringยป
hasOwnProperty vs in
in operator โ walks entire prototype chain
Challenge
Explore all 5 object types and observe where property lookup resolves in each chain.
Why Should I Care?
โ The exact interview question + the bug it kills.Interview questions
Q: Difference between obj.hasOwnProperty('x') and 'x' in obj?
| 1 | const obj = Object.create({ inherited: true }); |
| 2 | obj.own = true; |
| 3 | ย |
| 4 | obj.hasOwnProperty('own') // true โ checks only obj's own props |
| 5 | obj.hasOwnProperty('inherited') // false โ inherited, not own |
| 6 | obj.hasOwnProperty('toString') // false โ from Object.prototype |
| 7 | ย |
| 8 | 'own' in obj // true |
| 9 | 'inherited' in obj // true โ walks the chain! |
| 10 | 'toString' in obj // true โ found at Object.prototype! |
Rule: hasOwnProperty never climbs. in always climbs. Use hasOwnProperty when iterating โ otherwise you may accidentally process inherited enumerable properties.
Q: How does instanceof work?
| 1 | // Pseudocode โ what the engine does |
| 2 | function instanceof(obj, Constructor) { |
| 3 | let proto = Object.getPrototypeOf(obj); |
| 4 | while (proto !== null) { |
| 5 | if (proto === Constructor.prototype) return true; |
| 6 | proto = Object.getPrototypeOf(proto); // climb |
| 7 | } |
| 8 | return false; |
| 9 | } |
| 10 | ย |
| 11 | const d = new Dog(); |
| 12 | d instanceof Dog // true โ Dog.prototype in d's chain |
| 13 | d instanceof Animal // true โ Animal.prototype in d's chain |
| 14 | d instanceof Array // false โ not in d's chain |
instanceof literally walks the [[Prototype]] chain checking if Constructor.prototype appears anywhere. It is NOT about the constructor function โ you can fool it by swapping Constructor.prototype.
Q: Why can you add to Array.prototype and all arrays get it? Risk?
| 1 | // All arrays share ONE Array.prototype object |
| 2 | Array.prototype.sum = function() { |
| 3 | return this.reduce((a, b) => a + b, 0); |
| 4 | }; |
| 5 | [1, 2, 3].sum(); // 6 โ every array gets this immediately |
| 6 | ย |
| 7 | // THE RISK: for...in loop iterates ALL enumerable props |
| 8 | const arr = [1, 2, 3]; |
| 9 | for (const key in arr) { |
| 10 | console.log(key); // "0", "1", "2", "sum" โ leaked! |
| 11 | } |
| 12 | // Fix: |
| 13 | for (const key in arr) { |
| 14 | if (arr.hasOwnProperty(key)) console.log(key); // "0", "1", "2" only |
| 15 | } |
This is called monkey-patching. It's how older polyfills worked. Avoid in modern code โ it breaks for...in loops and risks collisions with future native methods.
Production bugs
Bug 1: for...in leaks prototype chain
| 1 | // โ WRONG โ iterates prototype keys too |
| 2 | const obj = Object.create({ base: true }); |
| 3 | obj.a = 1; |
| 4 | obj.b = 2; |
| 5 | for (const key in obj) { |
| 6 | data[key] = obj[key]; // picks up "base" from prototype! |
| 7 | } |
| 8 | ย |
| 9 | // โ FIX โ guard with hasOwnProperty |
| 10 | for (const key in obj) { |
| 11 | if (Object.prototype.hasOwnProperty.call(obj, key)) { |
| 12 | data[key] = obj[key]; // only own keys |
| 13 | } |
| 14 | } |
| 15 | // Or just use Object.keys() โ returns only own enumerable props |
| 16 | Object.keys(obj).forEach(key => { data[key] = obj[key]; }); |
Bug 2: Object.create(null) breaks toString
| 1 | // Common in hash map pattern |
| 2 | const cache = Object.create(null); |
| 3 | cache.key1 = "value"; |
| 4 | ย |
| 5 | // Works fine for storage: |
| 6 | cache['key1'] // "value" โ |
| 7 | ย |
| 8 | // Breaks anything using Object.prototype methods: |
| 9 | cache.toString() // TypeError: not a function! |
| 10 | cache.hasOwnProperty // undefined! |
| 11 | JSON.stringify(cache) // OK โ JSON uses its own path |
| 12 | ย |
| 13 | // Fix: explicitly call from Object.prototype when needed: |
| 14 | Object.prototype.hasOwnProperty.call(cache, 'key1') // true โ |
| 15 | String(cache) // "[object Object]" via explicit conversion |
The Deep Dive
โ Spec refs, engine internals, the minutiae.ES spec ยง10.1 โ OrdinaryObject internal methods
Every JavaScript object has a set of internal methods defined by the spec โ denoted with double brackets like [[Get]], [[Set]], [[HasProperty]]. These are not accessible from code โ they describe the engine's behavior.
[[Get]](P, Receiver) is what runs when you write obj.prop. The spec algorithm: (1) get own property descriptor; (2) if undefined, get parent via [[GetPrototypeOf]]; (3) if parent is null, return undefined; (4) call parent.[[Get]](P, Receiver) recursively. The chain walk is recursive delegation between internal method implementations.
| 1 | // Proxy lets you intercept these internal methods |
| 2 | const traced = new Proxy(obj, { |
| 3 | get(target, prop, receiver) { |
| 4 | console.log(`[[Get]] called for: ${String(prop)}`); |
| 5 | return Reflect.get(target, prop, receiver); // default [[Get]] |
| 6 | }, |
| 7 | getPrototypeOf(target) { |
| 8 | console.log("[[GetPrototypeOf]] called"); |
| 9 | return Reflect.getPrototypeOf(target); |
| 10 | }, |
| 11 | }); |
Property shadowing
When an own property has the same name as a prototype property, the own property shadows the prototype one. The chain walk stops at the first match.
| 1 | const parent = { greet: () => "Hello from parent" }; |
| 2 | const child = Object.create(parent); |
| 3 | child.greet = () => "Hello from child"; // shadows parent.greet |
| 4 | ย |
| 5 | child.greet() // "Hello from child" โ own property wins |
| 6 | parent.greet() // "Hello from parent" โ parent unaffected |
| 7 | ย |
| 8 | // Dangerous: accidentally shadowing builtins |
| 9 | const obj = { hasOwnProperty: () => false }; |
| 10 | obj.hasOwnProperty('x') // always false! own prop shadows built-in |
| 11 | // Safe call: |
| 12 | Object.prototype.hasOwnProperty.call(obj, 'x') // correct |
Performance โ chain length and V8 hidden classes
Each [[Prototype]] hop is a hashtable lookup. Long chains โ 5+ links โ cause measurable slowdowns in tight loops. V8 uses hidden classes (also called "shapes" or "maps") and inline caches to optimize repeat property accesses. If the shape of an object changes (e.g., properties added in different order), V8 deoptimizes and falls back to slower lookup.
Three ways to set [[Prototype]]
Object.create(proto)
| 1 | const child = Object.create(parent); |
| 2 | // child's [[Prototype]] === parent |
| 3 | // No constructor involved โ pure prototype linking |
Cleanest way. Explicit, no side effects.
new Constructor()
| 1 | function Dog() {} |
| 2 | const d = new Dog(); |
| 3 | // d's [[Prototype]] === Dog.prototype |
| 4 | // new creates a fresh object, sets [[Prototype]] to Dog.prototype, |
| 5 | // then calls Dog() with 'this' = the new object |
Classic constructor pattern. class is sugar over this.
class extends
| 1 | class Dog extends Animal {} |
| 2 | // Engine sets: Dog.prototype = Object.create(Animal.prototype) |
| 3 | // Plus wires up constructor and static inheritance |
Syntactic sugar โ most readable, recommended for class hierarchies.
Object.setPrototypeOf โ use with extreme caution
| 1 | const obj = {}; |
| 2 | Object.setPrototypeOf(obj, Array.prototype); // โ live mutation! |
| 3 | ย |
| 4 | // MDN warning verbatim: |
| 5 | // "Mutating the [[Prototype]] of an object is, by the nature |
| 6 | // of how modern JavaScript engines optimize property accesses, |
| 7 | // currently a very slow operation in every browser and JS engine." |
| 8 | // |
| 9 | // It invalidates ALL V8 inline caches for every object that |
| 10 | // ever had obj in its prototype chain โ not just obj itself. |
| 11 | // Use Object.create() at construction time instead. |
Object.create() or class extends. Never mutate an existing object's prototype chain with setPrototypeOf in production.Mixin pattern โ sharing without extending the chain
Instead of adding a long inheritance chain, copy methods directly onto the target prototype. No extra chain links โ faster lookup, no diamond inheritance issues.
| 1 | const Serializable = { |
| 2 | serialize() { return JSON.stringify(this); }, |
| 3 | deserialize(json) { return Object.assign(this, JSON.parse(json)); }, |
| 4 | }; |
| 5 | const Validatable = { |
| 6 | validate() { return Object.keys(this).every(k => this[k] != null); }, |
| 7 | }; |
| 8 | ย |
| 9 | // Mix in without extending the chain: |
| 10 | class User {} |
| 11 | Object.assign(User.prototype, Serializable, Validatable); |
| 12 | ย |
| 13 | const u = new User(); |
| 14 | u.serialize() // works โ now on User.prototype directly |
| 15 | u.validate() // works โ same |
| 16 | ย |
| 17 | // Chain stays short: u โ User.prototype โ Object.prototype โ null |
| 18 | // vs class hierarchy: u โ User.prototype โ Serializable.prototype |
| 19 | // โ Validatable.prototype โ Object.prototype โ null (slower) |
React class components used this pattern internally. Modern composition patterns (hooks, composables) go further โ they avoid the prototype chain entirely by closing over shared state in plain functions.
Interview Questions
โ Real questions from real interviews โ with answers.`hasOwnProperty` checks only own properties; `in` walks the entire prototype chain.
`instanceof` walks `obj`'s [[Prototype]] chain checking if `Constructor.prototype` appears anywhere.
An object with no [[Prototype]] โ no inherited keys, no `toString`, no `hasOwnProperty`.
An own property with the same name as a prototype property silently shadows it โ including inherited builtins like `hasOwnProperty` itself.
Mutating a live object's [[Prototype]] invalidates V8's inline caches for every object in that chain, not just the mutated object.
Memory Game
โ Quick quiz โ lock the concept in long-term memory.Which is the safest way to check if an object has an inherited property named `toString`?