Engineering Notes
Home/Languages/JavaScript/Prototype Chain
Languages · JavaScript

Prototype Chain

The prototype chain is the full sequence of linked objects the engine walks through when resolving a property. Every built-in type has its own chain terminating at null.

💻 Languages● Beginner⏱ 5 min read🛠️ 3 exercises🎤 5 interview Q&As

🎯 What you'll learn

  • What the Chain Actually Looks Like
  • Property Lookup Algorithm
  • Shadowing Along a Multi-Level Chain
  • Built-in Chains You Use Every Day
  • Walking the Chain Yourself

1What the Chain Actually Looks Like

The prototype chain is the full sequence of linked objects the engine walks through when resolving a property. Every built-in type has its own chain terminating at null.

text
myArray
  └─ Array.prototype     (push, map, filter, forEach, ...)
       └─ Object.prototype (toString, hasOwnProperty, valueOf, ...)
            └─ null        (chain ends here)
js
const arr = [1, 2, 3];
console.log(Object.getPrototypeOf(arr) === Array.prototype);            // true
console.log(Object.getPrototypeOf(Array.prototype) === Object.prototype); // true
console.log(Object.getPrototypeOf(Object.prototype));                    // null

This is why every array can call .push() (from Array.prototype) and .toString() (from Object.prototype, two links up).

myArray links to Array.prototype, which links to Object.prototype, which links to null
myArray links to Array.prototype, which links to Object.prototype, which links to null

2Property Lookup Algorithm

When you access obj.key, the engine performs this exact sequence:

  1. Does obj have an own property key? If yes, use it — stop.
  2. If not, set obj = Object.getPrototypeOf(obj). Is obj now null? If yes, return undefined — stop.
  3. Otherwise, go back to step 1.
js
const base = { greeting: "hi" };
const mid = Object.create(base);
const top = Object.create(mid);
top.name = "top-level";

console.log(top.name);     // step 1 succeeds immediately — own property
console.log(top.greeting); // step 1 fails on top, fails on mid, succeeds on base
console.log(top.missing);  // fails on all three, base's prototype is Object.prototype,
                            // Object.prototype's prototype is null -> undefined

3Shadowing Along a Multi-Level Chain

If more than one object in the chain has the same property name, the first (nearest) match wins — this is called shadowing.

js
const grandparent = { level: "grandparent" };
const parent = Object.create(grandparent);
parent.level = "parent"; // shadows grandparent.level for parent and anything below it

const child = Object.create(parent);

console.log(child.level); // "parent" — parent is nearer than grandparent

4Built-in Chains You Use Every Day

ValueChain
[] (array)Array.prototype → Object.prototype → null
function(){}Function.prototype → Object.prototype → null
"str" (auto-boxed)String.prototype → Object.prototype → null
new Map()Map.prototype → Object.prototype → null
class Dog extends Animal {} instanceDog.prototype → Animal.prototype → Object.prototype → null

class ... extends is exactly this mechanism — extends sets Dog.prototype's prototype to Animal.prototype, extending the chain by one more link.

5Walking the Chain Yourself

js
function describeChain(obj) {
  const chain = [];
  let current = obj;
  while (current !== null) {
    chain.push(current.constructor?.name ?? "(no constructor)");
    current = Object.getPrototypeOf(current);
  }
  return chain;
}

class Animal {}
class Dog extends Animal {}
console.log(describeChain(new Dog())); // ["Dog", "Animal", "Object"]

⚠️ Common Pitfalls

  1. Assuming the chain is "flat" — it can be arbitrarily deep; each extends or Object.create call adds one more link, and every lookup miss costs a traversal step.
  2. for...in walking the whole chain — it enumerates inherited enumerable properties too, unlike Object.keys() which only returns own properties. Always guard with hasOwnProperty if mixing the two matters.
  3. Modifying a prototype deep in the chain at runtime — instantly affects every object below it in the chain that hasn't shadowed that property, even objects created long ago.
  4. Confusing "own property lookup fails" with "property doesn't exist" — a failed own-property check just means step 2 (go up the chain) triggers; the property might still resolve further up.
Under the HoodWhy Chain Walks Don't Actually Cost O(depth) Every Time▾

Section 2's lookup algorithm — walk up one link, check, repeat — describes the correct observable behavior, but a naive engine that literally re-walked the chain on every single property access would be unacceptably slow for inherited properties (which are looked up far more often than own properties, since methods almost always live on a prototype). Real engines short-circuit this using the hidden-class and inline-cache machinery from the Prototypes topic, extended to cover the whole chain, not just an object's own shape.

Caching an inherited lookup's entire path

When an inline cache records a hit for an inherited property, it doesn't just remember an offset — it remembers which object in the chain actually held the property, tagged to the hidden class of the object the lookup started from (not the object where the property was found). On a cache hit, the engine can jump straight to the known holder without re-walking any intermediate links at all.

js
function Dog() {}
Dog.prototype.bark = function () { return "woof"; };

function callBark(d) { return d.bark(); } // this call site gets an IC

callBark(new Dog()); // first call: full O(depth) walk — finds bark() on Dog.prototype, caches it
callBark(new Dog()); // second call: SAME hidden class as before -> cache hit, no chain walk at all

Why mutating a prototype at runtime is expensive, not just "risky"

Section 6 of the earlier Prototypes topic warns that changing a shared prototype affects every object below it — the deeper reason this is also a performance cliff, not just a correctness one: adding, removing, or reordering a property on a prototype that's already been cached invalidates every inline cache that pointed at it. Every call site that had cached "found on Dog.prototype, offset N" must fall back to a slow, uncached lookup at least once, re-walk the chain for real, and rebuild its cache from scratch. Frameworks that monkey-patch built-in prototypes (Array.prototype.customMethod = ...) after the application has been running for a while can trigger exactly this kind of cache invalidation across every call site touching that type, application-wide.

hasOwnProperty and in bypass caching differently

"key" in obj and obj.hasOwnProperty("key") are still real chain-walking (or single-shape-check, for hasOwnProperty) operations — they benefit from hidden-class shape checks but are inherently less cacheable than a direct property read, because they're boolean existence checks, not value fetches tied to a single call site's "shape of things I've seen before" history in the same way.

📋 Quick Reference

text
obj.key lookup:
  own property?        → use it, stop
  → walk up one link    → repeat
  → reached null        → undefined

🎤 Interview Q&A

Questions interviewers ask about this topic. Answer out loud first, then open a card to compare.

Q1How does property lookup traverse the prototype chain?core▾

JS checks the object's own properties, then its prototype, then the prototype's prototype, until Object.prototype and finally null. Writing a property creates it on the object itself (shadowing), not on the prototype.

Follow-ups interviewers ask
  • Where does [].map come from? Array.prototype.
Q2How does inheritance work with classes under the hood?core▾

class B extends A sets B.prototype.__proto__ = A.prototype (instance methods) and B.__proto__ = A (static methods). super calls look up the parent's prototype.

Follow-ups interviewers ask
  • Are JS classes real classes? Syntax over prototypes, with some extra semantics.
Q3What does instanceof check?core▾

Whether Constructor.prototype appears anywhere in the object's prototype chain. It can fail across realms (iframes) and can be customized with Symbol.hasInstance.

Follow-ups interviewers ask
  • Safer array check? Array.isArray.
Q4Why are long prototype chains or prototype mutation slow?senior▾

Engines optimize objects with stable shapes and prototypes; changing an object's prototype at runtime (Object.setPrototypeOf) deoptimizes code.

Follow-ups interviewers ask
  • Recommendation? Set prototypes at creation (Object.create, classes).
Q5What's at the end of every prototype chain?core▾

Object.prototype, whose prototype is null, unless the object was created with Object.create(null).

Follow-ups interviewers ask
  • What methods come from there? toString, hasOwnProperty, valueOf…

🛠️ Practice exercises

Each exercise is a file in this topic folder. Code exercises are self-checking: run them and make the checks pass.