The Prototype Chain and Inheritance
Every Object Has a Hidden Link
When you access a property on an object and it doesn't exist, JavaScript doesn't return undefined immediately. It follows a hidden link to another object and looks there. Then follows that object's hidden link. And so on, until it reaches an object whose link is null. This chain of hidden links is the prototype chain, and it's the mechanism behind every method you've ever called on an array, string, or object.
Understanding this chain is the difference between knowing JavaScript and knowing JavaScript.
Imagine a family tree. When you ask someone "do you have this skill?", they first check themselves. If not, they ask their parent. The parent checks, then asks their parent. This continues until someone either has the skill or you reach the top ancestor (who has no parent). That's prototype lookup. Every object's [[Prototype]] is its "parent" in this lookup chain.
__proto__ vs .prototype — The Confusion
This is where most developers get lost. There are two completely different things with similar names:
__proto__ (or Object.getPrototypeOf(obj)): The internal [[Prototype]] link on every object. This is the hidden link JavaScript follows when looking up properties.
.prototype: A regular property that exists only on functions. When you call new Foo(), the new object's __proto__ is set to Foo.prototype.
function Dog(name) {
this.name = name;
}
Dog.prototype.bark = function() { return "Woof!"; };
const rex = new Dog("Rex");
// rex.__proto__ === Dog.prototype → true
// rex.prototype === undefined → true (rex is not a function)
// Dog.__proto__ === Function.prototype → true (Dog IS a function)
Property Lookup — The Walk
When you access rex.bark(), the engine performs the following lookup:
rex.bark();
// 1. Does rex own a property called "bark"? No.
// 2. Does rex.__proto__ (Dog.prototype) have "bark"? Yes. Call it.
rex.toString();
// 1. Does rex have "toString"? No.
// 2. Does Dog.prototype have "toString"? No.
// 3. Does Object.prototype have "toString"? Yes. Call it.
The chain for rex looks like:
rex → Dog.prototype → Object.prototype → null
Every lookup walks this chain. Property writes do NOT walk the chain — they always create/modify a property directly on the object (with one exception: setters).
Property assignment does not walk the prototype chain, but there's a subtle exception. If the prototype has a setter for that property, the setter runs instead of creating an own property. This can cause silent data loss:
const parent = {
set value(v) { console.log("setter called, value discarded"); }
};
const child = Object.create(parent);
child.value = 42;
// Logs: "setter called, value discarded"
// child.value is undefined — no own property was created!Object.create — Prototype Without Constructors
Object.create(proto) creates a new object with proto as its [[Prototype]]:
const animal = {
speak() { return `${this.name} makes a sound`; }
};
const dog = Object.create(animal);
dog.name = "Rex";
dog.speak(); // "Rex makes a sound"
// dog.__proto__ === animal → true
// dog has no "speak" own property — it's inherited
Object.create(null) creates an object with no prototype at all — no toString, no hasOwnProperty, nothing:
const dict = Object.create(null);
dict.toString; // undefined — no prototype chain
dict["__proto__"]; // undefined — not special here
// Useful for dictionaries where keys might be
// "constructor", "toString", "__proto__", etc.
Why Object.create(null) matters for security
When you use a plain object {} as a dictionary, keys like "__proto__", "constructor", and "toString" can collide with inherited properties. In a prototype pollution attack, user input gets assigned to __proto__, modifying the prototype of all objects. Object.create(null) eliminates this attack surface entirely. This is why many library internals (and Map) use it for key-value storage.
The Complete Prototype Chains
// Array instance chain:
[1, 2, 3]
→ Array.prototype (has push, map, filter, etc.)
→ Object.prototype (has toString, hasOwnProperty, etc.)
→ null
// Function chain:
function foo() {}
// foo → Function.prototype → Object.prototype → null
// The twist:
Object.getPrototypeOf(Function.prototype) === Object.prototype; // true
Object.getPrototypeOf(Object.prototype) === null; // true
// The circular-looking part:
Function.__proto__ === Function.prototype; // true
// Function is an instance of itself. This is a bootstrap
// quirk — both Function and Object are created by the engine
// before normal object creation rules apply.
Why Modifying Built-in Prototypes Is Dangerous
// "Helpful" utility — DON'T DO THIS
Array.prototype.last = function() {
return this[this.length - 1];
};
[1, 2, 3].last(); // 3 — works!
// But now every for...in over any array includes "last":
const arr = [1, 2, 3];
for (const key in arr) {
console.log(key); // "0", "1", "2", "last"
}
// And if any library also defines Array.prototype.last
// with different behavior, one silently overwrites the other.
// This is why MooTools broke the web and forced TC39
// to rename Array.prototype.flatten to .flat
In 2018, TC39 wanted to add Array.prototype.flatten() to the language. But MooTools (a popular library from 2007) had already monkey-patched Array.prototype.flatten with incompatible behavior. Shipping the new native method would break millions of sites still using MooTools. TC39 was forced to rename it to Array.prototype.flat(). This is called "don't break the web" — and it's why you never modify built-in prototypes.
Production Scenario: The hasOwnProperty Problem
// API returns user data as a plain object
const userData = JSON.parse(apiResponse);
// Bug: what if the API sends { "hasOwnProperty": "hacked" }?
userData.hasOwnProperty("name"); // TypeError: not a function
// Safe alternative:
Object.prototype.hasOwnProperty.call(userData, "name");
// Even better (ES2022):
Object.hasOwn(userData, "name"); // true/false, never throws
| What developers do | What they should do |
|---|---|
| Confusing __proto__ with .prototype Every object has __proto__. Only functions have .prototype. | __proto__ is the lookup link on instances. .prototype is a property on constructor functions. |
| Modifying Array.prototype or Object.prototype Monkey-patching built-in prototypes breaks for...in, conflicts with other libraries, and can break future spec additions | Use utility functions, subclasses, or Symbol-keyed methods |
| Using obj.hasOwnProperty() on arbitrary objects The object might have its own hasOwnProperty property that shadows the inherited method | Use Object.hasOwn(obj, key) or Object.prototype.hasOwnProperty.call(obj, key) |
| Thinking property assignment walks the chain Only reads walk the chain. Writes go directly on the object. | Assignment creates an own property (except when a setter exists on the prototype) |
- 1__proto__ is the lookup link on every object. .prototype is a property on constructor functions that becomes __proto__ of instances created with new.
- 2Property reads walk the prototype chain. Property writes create own properties on the object directly (unless a setter is found on the chain).
- 3Object.create(proto) sets the prototype explicitly — use Object.create(null) for safe dictionaries with no inherited properties.
- 4Never modify built-in prototypes — it breaks for...in, conflicts with libraries, and can collide with future spec additions.
- 5Use Object.hasOwn(obj, key) instead of obj.hasOwnProperty(key) — it's safe against shadowed methods.