Chapter 7.3☕ 20 min

Prototype Chain: __proto__ vs prototype Deep Dive

Property nahi mila? Dada se pucho! Yeh hai prototype chain — JS ka asli DNA.

01The Prototype Chain: How Property Lookup Works

Every object has an internal link to another object: its prototype. When you access a property that doesn't exist on the object itself, JavaScript follows this link and looks in the prototype. If it's not there either, it follows the next link. This continues until the property is found or the chain ends at null.

This is the delegation model — objects delegate property lookups to their prototypes. It's like asking your father, who asks his father, who asks his father... until someone has the answer or there's nobody left to ask.

const grandpa = { wisdom: 'Old school' };
const parent = Object.create(grandpa);
parent.money = 100;
const child = Object.create(parent);
child.name = 'Sai';

console.log(child.name);    // 'Sai' (own property)
console.log(child.money);   // 100 (found on parent)
console.log(child.wisdom);  // 'Old school' (found on grandpa)

// The lookup chain:
// child -> parent -> grandpa -> Object.prototype -> null
End of the chain: Object.prototype.__proto__ === null. Object.prototype is the final link. Every ordinary object's chain eventually reaches Object.prototype and then null. This is why all objects have methods like toString() and hasOwnProperty() — they're defined on Object.prototype.
02__proto__ vs prototype: The Big Confusion

These two are the most confused concepts in JavaScript. Let's clear it once and for all.

__proto__ is an accessor property on every object. It points to the object's prototype — the object it delegates to. It is the actual link in the prototype chain.

.prototype is a property on only function objects. It is the object that will become the __proto__ of objects created with new.

Key rule: obj.__proto__ === Constructor.prototype (if obj was created via new Constructor). A function's .prototype is NOT its own __proto__. The function's __proto__ is Function.prototype.
function User(name) {
  this.name = name;
}
User.prototype.greet = function() {
  return 'Hi, ' + this.name;
};

const sai = new User('Sai');

// __proto__ vs prototype
console.log(sai.__proto__ === User.prototype); // true!
// sai does NOT have a .prototype property (only functions do)

// User's own __proto__ points to Function.prototype
console.log(User.__proto__ === Function.prototype); // true
// User's .prototype is what instances will inherit
console.log(typeof User.prototype); // 'object'

// The instance chain:
// sai -> User.prototype -> Object.prototype -> null
V8 internals: V8 stores the prototype pointer in the object's Hidden Class Map. When a property lookup misses the object's own properties, V8 follows this pointer to the prototype's Map and looks there. This is O(chain length). Deep chains are slow.
03Property Shadowing (Overriding)

If an object and its prototype both have the same property name, the object's own property "shadows" the prototype's.

Reading: Finds the own property first and stops looking up the chain.

Writing: Always affects the own property (creates it if it doesn't exist), never the prototype's.

The only exception: setters on the prototype can intercept writes.

const proto = {
  greet() { return 'Hello from proto'; },
  name: 'ProtoName'
};

const obj = Object.create(proto);
obj.name = 'ObjName'; // Creates OWN property, shadows proto.name

console.log(obj.name);         // 'ObjName' (own property wins)
console.log(obj.greet());      // 'Hello from proto' (inherited)

// Deleting the own property reveals the prototype's
delete obj.name;
console.log(obj.name); // 'ProtoName' (proto's name is visible again!)

// Writing to an inherited property NEVER changes the prototype
obj.greet = function() { return 'Overridden!'; };
console.log(proto.greet()); // 'Hello from proto' (unchanged)
04Object.create(): Pure Prototypal Inheritance

Object.create(proto) creates a new object with the specified prototype. No constructor function needed — pure prototypal inheritance.

Object.create(null) creates an object with no prototype at all — a pure dictionary with no toString, no hasOwnProperty, nothing inherited.

Object.create(proto, propertyDescriptors) can also define properties on creation using the same descriptor format as Object.defineProperty.

// Prototypal inheritance without constructors
const animal = {
  speak() { return this.sound; }
};

const dog = Object.create(animal);
dog.sound = 'Woof!';
console.log(dog.speak()); // 'Woof!'

// The "null prototype" object — no inherited methods
const dict = Object.create(null);
dict.key = 'value';
// dict.toString(); // TypeError! No prototype means no methods.
// dict.hasOwnProperty('key'); // TypeError!
console.log('key' in dict); // true (in operator works)
console.log(Object.hasOwn(dict, 'key')); // true (safe check)

// Why Object.create(null)? Perfect for dictionaries.
// No risk of colliding with Object.prototype properties like 'toString'.
05Modifying Prototypes: Performance Disaster

Object.setPrototypeOf(obj, newProto) changes an object's prototype after it has been created. NEVER DO THIS IN PRODUCTION.

Why? Because changing a prototype means V8 must throw away the old Hidden Class and create a new one. V8 then has to de-optimize all Inline Caches that cached the old prototype chain.

The only safe time to set a prototype is during object creation.

const fast = { a: 1 };
// fast's hidden class and ICs are optimized

// THE PERFORMANCE KILLER
const newProto = { b: 2 };
Object.setPrototypeOf(fast, newProto);
// V8: "The whole hidden class chain is invalid!"
// V8: "I must de-optimize every function that touched 'fast'."

// Safe way: Set prototype at creation
const safe = Object.create(newProto);
safe.a = 1; // Prototype is fixed, hidden class is stable.
📋 Rule: If you need to change an object's prototype, you should probably be using a different pattern (like composition or a factory function). Changing prototypes at runtime is a code smell and a performance killer.

Lo kar liya — Key Points:

  • ✅ Prototype chain: Objects delegate property lookups to their prototype, which delegates to its prototype, until null.
  • __proto__ is the ACTUAL link on every object; .prototype is a property on functions used by new.
  • ✅ Property shadowing: An own property hides a prototype property of the same name. Writes never modify the prototype.
  • Object.create(proto) creates a new object with the given prototype; Object.create(null) creates a pure dictionary.
  • Object.setPrototypeOf is extremely slow; it invalidates V8 hidden classes and inline caches. Avoid it.
  • ✅ Prototype chain lookups are O(chain length); deep chains are slower than shallow ones.
Course Search
Search across all chapters & stages
📖

Search the course

Type any topic — branching, stash, rebase, hooks — and jump straight to that chapter.

merge branchesgit stashundo commitrebase