A deep dive into how objects link together in JavaScript, how __proto__
and prototype
really work, and why understanding the prototype chain saves you from bugs and design mistakes.

__proto__
and prototype
really work, and why understanding the prototype chain saves you from bugs and design mistakes.Introduction
JavaScript doesn’t have “classes” in the same sense as Java or C++. Under the hood, it’s all about objects linked to other objects. This linking is called the prototype chain, and it’s the backbone of inheritance in JS.
Every object in JavaScript has a hidden property ([[Prototype]]
) that points to another object. If you try to access a property that doesn’t exist on the current object, JS walks up the chain until it finds it—or hits the end (null
).
Mastering this concept means:
- You’ll debug weird “why is this property available?” bugs.
- You’ll write efficient object models (before and beyond
class
). - You’ll grok why
Object.create
,new
, andclass
all work the way they do.
Let’s unpack it step by step with real code.
1. Objects and Their Hidden Link
Every JS object has an internal slot called [[Prototype]]
. You can peek at it with Object.getPrototypeOf(obj)
or obj.__proto__
(the non-standard accessor).
const animal = { eats: true };
const dog = { barks: true };
Object.setPrototypeOf(dog, animal);
console.log(dog.barks); // true (own property)
console.log(dog.eats); // true (inherited from animal)
If dog
doesn’t have eats
, JS looks up its prototype: animal
.
2. Walking the Prototype Chain
console.log(dog.toString);
Where does toString
come from?
- Not on
dog
. - Not on
animal
. - Found on
Object.prototype
.
At the top:
Object.getPrototypeOf(Object.prototype); // null
👉 That’s the end of the chain.
3. Functions and the prototype
Property
Functions in JS are special objects: they have a prototype
property that’s used when you call them with new
.
function Animal(name) {
this.name = name;
}
Animal.prototype.eats = true;
const cat = new Animal("Kitty");
console.log(cat.name); // "Kitty" (own property)
console.log(cat.eats); // true (inherited from Animal.prototype)
Mechanics:
new Animal("Kitty")
creates a blank object.- Sets its
[[Prototype]]
toAnimal.prototype
. - Runs
Animal
withthis
bound to the new object.
4. Prototype Chain in Classes (Sugar Syntax)
ES6 class
is just syntactic sugar around the same mechanism.
class Animal {
constructor(name) { this.name = name; }
speak() { console.log(this.name + " makes a sound"); }
}
class Dog extends Animal {
speak() { console.log(this.name + " barks"); }
}
const rex = new Dog("Rex");
rex.speak(); // "Rex barks"
Behind the scenes:
Dog.prototype.__proto__ === Animal.prototype
.Animal.prototype.__proto__ === Object.prototype
.
So rex
can access methods from Dog
, then Animal
, then Object
.
5. Visualizing the Chain
rex
↑ [[Prototype]]
Dog.prototype
↑ [[Prototype]]
Animal.prototype
↑ [[Prototype]]
Object.prototype
↑ [[Prototype]]
null
6. Shadowing and Property Lookup
If an object has its own property with the same name as an inherited one, it shadows it.
const base = { greet: () => "hi" };
const child = Object.create(base);
child.greet = () => "hello";
console.log(child.greet()); // "hello" (own shadows prototype)
delete child.greet;
console.log(child.greet()); // "hi" (falls back to prototype)
7. Real-World Examples
Custom Methods via Prototype
Array.prototype.first = function() {
return this[0];
};
console.log([10, 20, 30].first()); // 10
(Extending built-ins is risky — can break polyfills or libraries.)
Polyfills
if (!String.prototype.includes) {
String.prototype.includes = function(sub) {
return this.indexOf(sub) !== -1;
};
}
React/Node Class Hierarchies
React components: class MyComponent extends React.Component {}
.
Node’s EventEmitter
: many core modules inherit from it.
8. Object.create
— Pure Prototype Magic
const animal = { eats: true };
const rabbit = Object.create(animal);
console.log(rabbit.eats); // true (from prototype)
console.log(Object.getPrototypeOf(rabbit) === animal); // true
Great for lightweight inheritance without constructors.
9. Performance Considerations
- Prototype lookups are fast (optimized in modern engines).
- But deep chains make debugging harder.
- Avoid modifying prototypes of built-ins in libraries — can cause collisions.
10. Common Gotchas
Arrow Functions on Prototypes
function Foo() {}
Foo.prototype.bar = () => this;
console.log(new Foo().bar()); // ❌ not Foo instance, it's outer `this`
Use normal functions on prototypes.
Misunderstanding __proto__
vs prototype
obj.__proto__
→ the prototype of an object (its hidden link).Func.prototype
→ the object used as the prototype for new instances created withnew Func()
.
Static Methods
class A {
static greet() { return "hi"; }
}
console.log(A.greet()); // "hi"
console.log(new A().greet()); // ❌ TypeError
Static methods live on the constructor function itself, not on .prototype
.
11. Quick Reference Table

Conclusion
The prototype chain is the engine of JavaScript’s inheritance. Instead of copying methods into each object, JS links them through [[Prototype]]
. This means:
- Objects share behavior efficiently.
- Classes and
extends
are just sugar for prototype links. - You can inspect and manipulate prototypes directly with
Object.create
,Object.getPrototypeOf
, andObject.setPrototypeOf
.
Pro tip: When debugging property lookups, ask: Does this belong to the object itself, or is it coming from somewhere higher in the chain? That question alone saves hours of confusion.
Call to Action
What’s the weirdest prototype bug you’ve hit — shadowed props, lost this
, or __proto__
confusion?
💬 Share in the comments.
🔖 Bookmark this as your prototype cheat sheet.
👩💻 Send to a teammate who still thinks class
is “real inheritance” in JS.
Leave a Reply