Walking the prototype chain
One more clause completes the property rules. When an object lacks a property, JavaScript doesn't give up — it follows a special __proto__ wire to another object and keeps looking. Reading walks the chain; writing never does. Watch it happen.
A __proto__ is a special wire from one object to another, its prototype. When a property is missing, the search continues there:
let mammal = { furry: true }; let human = { __proto__: mammal, teeth: 32 }; let gwen = { __proto__: human, age: 19 }; console.log(gwen.age); // 19 — own property console.log(gwen.teeth); // 32 — found on human console.log(gwen.furry); // true — found on mammal console.log(gwen.swims); // undefined — nobody has it
Pick a property in the lab and step the search. Toggle shadowing to give gwen its own teeth and watch the walk stop early.
How a missing property is resolved, hop by hop
JavaScript checks the object for an own property; if it's missing, it follows __proto__ to the next object and checks again — until it finds the property or runs out of chain (null), which yields undefined.
The __proto__ wire
A prototype is just another object. The __proto__ wire says "if you can't find it here, look there next."
This adds one clause to the rules from Properties. When we look up obj.prop and the object has no own prop, we don't immediately answer undefined. First we check whether the object has a __proto__ wire. If it does, we follow it and repeat the search on that object. We keep hopping until the property is found or an object has no prototype (its __proto__ is null) — only then is the result undefined.
gwen → human → mammal → … A prototype chain can be many objects long, but it can never be circular — JavaScript won't let an object end up as its own ancestor.
Shadowing: own wins
If an object has its own copy of a property, the search stops there. The prototype's version still exists — it's just hidden.
let human = { teeth: 32 }; let gwen = { __proto__: human, teeth: 20 }; console.log(gwen.teeth); // 20 — gwen's own wins console.log(human.teeth); // 32 — unchanged
gwen.teeth finds an own property immediately and never reaches human. We say gwen's teethshadows human's. The prototype is not modified; the search simply stops earlier. Toggle the shadow in the lab, then re-run gwen.teeth to see the walk end at hop zero.
hasOwnProperty To ask whether a property belongs to an object itself rather than its chain, use gwen.hasOwnProperty('teeth'). It answers true only for own properties — the prototype's teeth doesn't count.
Writing never walks the chain
Reading traverses prototypes. Assignment does not — it always creates or updates an own property on the object you wrote to.
let human = { teeth: 32 }; let gwen = { __proto__: human }; gwen.teeth = 20; // creates gwen's OWN teeth console.log(gwen.teeth); // 20 console.log(human.teeth); // 32 — prototype untouched
Even though gwen had no own teeth, the assignment did not climb to human and edit it. It created a brand-new own teeth property directly on gwen. From then on, reads of gwen.teeth find that own property first. This is the same shadowing as before — produced by a write rather than a literal.
Readingobj.prop walks the chain until it finds the property or hits null (then undefined). Writingobj.prop = x always sets an own property on obj itself.
The Object Prototype
Plain objects aren't the end of the chain. They point at a shared Object Prototype — which is where methods like hasOwnProperty come from.
Make an empty object and inspect its prototype:
let obj = {}; console.log(obj.__proto__); // the Object Prototype console.log(obj.toString); // a function — inherited from it
Every object literal's __proto__ points by default at the same special object, the Object Prototype. That's why obj.hasOwnProperty(...) and obj.toString() work on objects you never gave those methods — they're found one hop up the chain. The Object Prototype is the top: its own __proto__ is null, so the search ends there.
You can opt out entirely with an explicit null prototype:
let bare = { __proto__: null }; console.log(bare.toString); // undefined — no prototype to inherit from
Step gwen.swims in the lab: the walk passes human, mammal, then the Object Prototype, then reaches null — and only there does it return undefined.
Prototype pollution & the Pineapple Pizza
Because the Object Prototype is shared by almost every object, mutating it affects everything. This is a footgun, not a feature.
If you mutate the Object Prototype, every plain object suddenly appears to have the property you added:
obj.__proto__.smell = 'banana'; // mutates the SHARED Object Prototype let anything = {}; console.log(anything.smell); // 'banana' (!)
This is prototype pollution. One mutation leaks into every object whose chain ends at the Object Prototype — which is nearly all of them. It is a classic source of bugs and security holes. Avoid mutating prototypes you don't own.
let pizza = {}; console.log(pizza.taste); // could this print "pineapple"?
pizza has no own taste, so the read walks to its prototype — the Object Prototype. Normally taste isn't there either, so the result is undefined. But if some earlier code ran ({}).__proto__.taste = 'pineapple', that property now lives on the shared prototype, and pizza.taste finds it. The value was never "in" pizza — the rules walked the chain and found it one hop up.
__proto__ vs prototype, and recap
Two confusingly-named things. The wire that drives lookups is __proto__; the ordinary property called prototype lives on functions and is used by new.
__proto__ is the lookup mechanism — the wire every object follows when a property is missing. prototype is a regular property that functions happen to have; the new operator and class syntax use it to set up the __proto__ wire of the objects they create. Modern code rarely touches __proto__ directly: classes hide all of this behind cleaner syntax, but underneath, the same chain-walking rules are doing the work.
- A
__proto__is a wire from an object to its prototype — another object. - Reading a missing property follows
__proto__and searches again, until found ornull(thenundefined). - An own property shadows the prototype's;
hasOwnPropertychecks own only. - Writing
obj.prop = xnever walks the chain — it always sets an own property onobj. - Plain objects inherit from the shared Object Prototype (whose
__proto__isnull); mutating it pollutes everything. __proto__(the wire) is not the same asprototype(a property on functions, used bynewand classes).
Wires, values, the JavaScript universe, equality, properties, mutation, and now prototypes — that's the whole mental model. Every expression you read from here on is just these rules, applied carefully.