JavaScript Prototype Chain Explained: How Objects Inherit
Every JavaScript object links to a prototype, and property lookup walks that chain until null. Shadowing, Object.create, instanceof and prototype pollution.
- Course: JavaScript study plan
- Module: Prototypes and classes
- Kind: Lesson
- Reading time: 13 min
- Runtime: Node 16
What is the prototype chain in JavaScript?
The prototype chain is how JavaScript objects inherit. Every object has a hidden link to another object, its prototype; reading a property the object does not have walks up those links until the property is found or the chain ends at null. Methods live once on a shared prototype, writes create own properties that shadow inherited ones, and instanceof asks whether a prototype is on the chain.
Lesson
JavaScript has no classes in the Java sense — class is syntax over something older and simpler. Every object has a hidden link to another object, its prototype; when you read a property the object does not have, the engine follows the link and looks there, then at that object's prototype, and so on until it hits null. That walk is the whole inheritance system. Methods live once on a shared prototype rather than being copied into every instance, instanceof is a question about that chain, and half the "why does toString exist on my empty object" surprises are answered by drawing it. This lesson makes the chain explicit: how to read and set it, how lookup and shadowing work, and the built-in chains you are already standing on.
Every object has a prototype
const animal = { eats: true, describe() { return `${this.name} eats: ${this.eats}`; } };
const rabbit = Object.create(animal); // rabbit's prototype is animal
rabbit.name = "Peter";
rabbit.eats; // true — not on rabbit; found on animal
rabbit.describe(); // "Peter eats: true" — this is rabbit, the object the call started from
Object.getPrototypeOf(rabbit) === animal; // true
Object.getPrototypeOf(animal) === Object.prototype; // true — literals inherit from Object.prototype
Object.getPrototypeOf(Object.prototype); // null — the end of every chain
Object.create(proto) makes an empty object whose prototype is proto. Object.getPrototypeOf(obj) reads the link; Object.setPrototypeOf(obj, proto) rewrites it (slow — engines deoptimise objects whose prototype changes after creation; set it at creation instead). The __proto__ accessor (rabbit.__proto__) is the legacy spelling of the same link; read it in the console, do not write it in code.
Lookup and shadowing
Reading obj.x checks obj's own properties first, then walks up. Writing obj.x = v always creates or updates an own property on obj — it never modifies the prototype — so a write on an instance shadows the inherited value:
rabbit.eats = false; // own property now; animal.eats is still true
delete rabbit.eats; // the shadow goes; rabbit.eats reads true again from animal
That asymmetry is why Object.hasOwn(obj, key) and in give different answers: "eats" in rabbit is true (found on the chain), Object.hasOwn(rabbit, "eats") is false until it is shadowed. for…in walks inherited enumerable properties too — one more reason Object.keys (own, enumerable) is the loop you want.
Two subtleties. Getters and setters on the prototype run with this set to the receiver — a prototype setter does not create a shadow; it runs. And a method found up the chain is called with this being the object you started from, which is exactly what makes shared methods work on per-instance data.
The chains you already use
Object.getPrototypeOf([]) === Array.prototype; // then Object.prototype, then null
Object.getPrototypeOf(function () {}) === Function.prototype;
Object.getPrototypeOf("s") === String.prototype; // primitives are boxed for the lookup
Object.getPrototypeOf(Object.create(null)); // null — an object with NO prototype
[1, 2].map is found on Array.prototype; "abc".toUpperCase on String.prototype after a temporary box; {}.toString on Object.prototype. Object.create(null) produces a true dictionary with nothing inherited — no toString, no constructor, no __proto__ hazard — which is why it appears in code that maps untrusted keys.
instanceof is a chain question
x instanceof F asks: does F.prototype appear anywhere in x's prototype chain? Nothing about "type", nothing about how x was made:
rabbit instanceof Object; // true — Object.prototype is on the chain
[] instanceof Array; // true
Object.create(null) instanceof Object; // false — empty chain
The next lesson connects this to constructor functions (F.prototype is the object new F() installs as the prototype). Symbol.hasInstance lets a class customise the check — rare, but it is why instanceof can lie.
Modifying built-in prototypes
Array.prototype.last = function () { return this[this.length - 1]; } works — every array gains last. It is also how libraries have broken each other for twenty years: two of them define last differently, a future standard adds a real last with other semantics, for…in over arrays starts yielding "last". Polyfills (adding a standard method that an old engine lacks) are the accepted exception; anything else is a hazard. Write a function that takes the array.
Prototype pollution
When object keys come from user input — settings[userKey] = value, a deep merge of a parsed JSON body — a key of "__proto__" can write to Object.prototype itself, and every object in the process suddenly has that property. Guards: Object.create(null) for dictionaries, Map for anything keyed by input, and merge functions that skip __proto__, constructor and prototype. Several real CVEs in popular libraries were exactly this.
Common mistakes
- Expecting
obj.x = 1to change the prototype'sx— it shadows on the instance. - Using
for…inon objects that have (or might gain) inherited enumerable properties. Object.setPrototypeOfon hot objects — set the prototype at creation.- Extending
Object.prototypeorArray.prototypewith helpers. - Thinking
instanceofchecks a class name; it checks a prototype object, which fails across two copies of a library.
Interview angle
- "How does inheritance work in JavaScript?" Delegation: every object links to a prototype; missing properties are looked up along the chain to
null. - "Difference between
__proto__andprototype?"__proto__is an object's link to its prototype;prototypeis a property of functions — the objectnew F()will link to. (Next lesson.) - "What does
instanceofcheck?" WhetherF.prototypeis on the object's chain. - "How do you create an object with no prototype, and why?"
Object.create(null)— a clean dictionary immune to inherited names and pollution. - "What is prototype pollution?" Writing through
__proto__from untrusted keys soObject.prototypegains properties.
Key takeaways
- Every object has a prototype link; reads walk it, writes never do — they shadow on the instance.
Object.create(proto),Object.getPrototypeOf,Object.hasOwnversusin,Object.keysfor own enumerable.- Built-ins: arrays →
Array.prototype→Object.prototype→null; primitives box for the lookup. instanceof= "isF.prototypeon the chain";Object.create(null)has an empty chain.- Never extend built-in prototypes; guard user-supplied keys against
__proto__.
Common questions
What is the difference between __proto__ and prototype in JavaScript?
__proto__ is an object's link to its own prototype — the legacy accessor for what Object.getPrototypeOf returns. prototype is a property of functions: the object that instances made with new F() will link to. They are different links, so F.prototype is not the prototype of F itself.
What does instanceof check in JavaScript?
x instanceof F checks whether F.prototype appears anywhere in the prototype chain of x. It says nothing about how x was made, so Object.create(null) instanceof Object is false, and objects from two copies of the same library can fail the check.
What is the difference between in and Object.hasOwn?
The in operator is true when the property is found anywhere on the prototype chain; Object.hasOwn(obj, key) is true only for the object's own properties. An inherited property therefore passes in but fails Object.hasOwn until an own property shadows it.
Why use Object.create(null) in JavaScript?
Object.create(null) makes an object with no prototype at all: nothing inherited, no toString, no constructor, no __proto__ accessor. That makes it a clean dictionary for keys that come from user input, where an inherited name or a "__proto__" key could otherwise misbehave.
What is prototype pollution?
Prototype pollution happens when untrusted keys — a "__proto__" key in a parsed JSON body passed to a deep merge, say — write to Object.prototype, so every object in the process gains that property. Guard with Object.create(null) or a Map for input-keyed data, and merges that skip __proto__, constructor and prototype.
Exercises
A prototype-chain resolver
Build objects from commands and resolve lookups the way the engine does. def <name> <parent|-> [key value]... creates an object with Object.create (parent - means a prototype of null) and assigns the pairs as own properties. get <name> <key> prints <name>.<key> = <value> (own) when the property is the object's own, (from <owner>) naming the prototype that has it, or <name>.<key> = undefined when nothing on the chain does. set <name> <key> <value> assigns on that object. chain <name> prints the chain's names joined by -> .
Example:
def animal - eats yes legs 4
def rabbit animal name peter
def baby rabbit
get baby name
get baby eats
set baby eats no
get baby eats
get rabbit eats
get baby wings
chain baby
→
baby.name = peter (from rabbit)
baby.eats = yes (from animal)
baby.eats = no (own)
rabbit.eats = yes (from animal)
baby.wings = undefined
baby -> rabbit -> animal
The set shadows on baby; rabbit and animal are untouched.
Own versus inherited
The first line is a JSON prototype, the second a JSON child; the starter creates the child with Object.create(proto) and copies its own properties on. Print keys=<Object.keys(child)>, then forIn=<every key a for…in loop visits>, then for each key in the union (proto keys first, then child keys, no repeats) <key>: in=<key in child> own=<Object.hasOwn> value=<child[key]>. Finally delete every own property of the child that also exists on the prototype and print unshadowed: <key>=<value now read>, ... (or unshadowed: - when nothing was shadowing).
Example: {"eats":true,"legs":4} then {"name":"peter","legs":3} →
keys=name,legs
forIn=name,legs,eats
eats: in=true own=false value=true
legs: in=true own=true value=3
name: in=true own=true value=peter
unshadowed: legs=4In this module: Prototypes and classes
- The prototype chain — how JavaScript objects inherit (this lesson)
- Constructor functions and what `new` really does
- Class syntax — fields, methods, statics, private and accessors
- Inheritance — extends, super, overriding and extending built-ins
- Property descriptors and the object protocols — toString, valueOf, toJSON, Symbol.toPrimitive
- Checkpoint — Prototypes and classes
← Checkpoint — Objects, arrays and destructuring · Constructor functions and what `new` really does →