Prototypes and the Prototype Chain
TL;DR
Every JS object has a hidden link, [[Prototype]], to another object. Property lookup walks this chain until it finds the key or hits null. Functions have a .prototype object that becomes the [[Prototype]] of instances created with new. class is syntax sugar over this — methods live on Class.prototype, shared by all instances. The senior-level points: __proto__ (the accessor) vs [[Prototype]] (the internal slot), why Object.setPrototypeOf is a performance trap, and why you check Object.hasOwn instead of trusting inherited keys.
In depth
What is the prototype chain?
A linked list of objects. When you read obj.x, the engine checks obj’s own properties, then obj’s prototype, then its prototype, up to Object.prototype, then null.
const animal = { eats: true };
// dog.[[Prototype]] === animal
const dog = Object.create(animal);
dog.barks = true;
dog.barks; // true (own)
dog.eats; // true (inherited from animal)
dog.toString; // function (inherited from Object.prototype)Writes, by contrast, almost always create an own property on the object — they don’t modify the prototype (except for inherited setters).
__proto__ vs prototype vs [[Prototype]]?
[[Prototype]]— the actual internal slot every object has.__proto__— a legacy accessor (getter/setter onObject.prototype) exposing that slot. Standardized for the web but discouraged; preferObject.getPrototypeOf/Object.setPrototypeOf.Constructor.prototype— a property on functions. It’s the object that becomesinstance.[[Prototype]]when you callnew Constructor(). It is not the function’s own prototype.
function Dog() {}
const d = new Dog();
Object.getPrototypeOf(d) === Dog.prototype; // trueHow does class map onto prototypes?
Methods go on .prototype (shared); fields go on the instance (per-object); static members go on the constructor itself.
class Dog {
legs = 4; // own, per-instance
bark() {} // Dog.prototype.bark — shared
static species() {} // Dog.species
}
const a = new Dog(), b = new Dog();
// true — same function on the prototype
a.bark === b.bark;extends sets Sub.prototype.[[Prototype]] = Base.prototype (instance chain) and Sub.[[Prototype]] = Base (static inheritance).
Why is Object.setPrototypeOf (and __proto__=) a performance trap?
Engines optimize property access with hidden classes / inline caches tied to an object’s shape, which includes its prototype. Mutating the prototype of a live object invalidates those caches and can deopt every access site that touched it. Set the prototype at creation (Object.create(proto) or a class) — never mutate it later on hot objects.
How do you check whether a property is own vs inherited?
// preferred (ES2022)
Object.hasOwn(obj, "x");
Object.prototype.hasOwnProperty.call(obj, "x"); // pre-2022, safe even if obj has its own `hasOwnProperty`
// true for inherited too
"x" in obj;for...in iterates inherited enumerable keys; Object.keys returns only own enumerable keys. Prefer Object.keys/entries to avoid surprises.
How do you create an object with no prototype, and why?
Object.create(null) — a “dictionary” object with no inherited methods. Used for safe key/value maps so user-supplied keys like "toString" or "__proto__" don’t collide with Object.prototype (a prototype-pollution defense). Downside: no toString, hasOwnProperty, etc. — use Object.hasOwn(obj, k) and Map where possible.
instanceof — how does it work, and when does it lie?
x instanceof C walks x’s prototype chain looking for C.prototype. It breaks across realms (an array from an <iframe> is not instanceof the parent’s Array) — use Array.isArray, Object.prototype.toString.call(x), or Symbol.hasInstance overrides knowingly.
Gotchas / edge cases
- Mutating
Array.prototype/Object.prototypebreaks the world —for...inthen iterates your addition, libraries collide. Never extend built-in prototypes in app code. - Prototype pollution —
obj[userKey] = userValwithuserKey === "__proto__"can poisonObject.prototype. Validate keys, useObject.create(null)orMapfor untrusted data. See Frontend Security — Senior Interview Prep. - Shared mutable state on the prototype — putting an object/array on
.prototypeshares one instance across all objects:Dog.prototype.tricks = []means every dog shares the same array. Put mutable state on instances. Object.create(proto, descriptors)uses property descriptors, not plain values — easy to write{ x: 1 }and get a property whose value is{ value: undefined }unless you write{ x: { value: 1 } }.- Arrow functions and methods have no
.prototype— onlyfunction/classconstructors do;new (() => {})()throws.
What a senior is expected to say 4
- “Lookup walks
[[Prototype]]; writes create own properties.classmethods live on the shared.prototype, fields on the instance.” - “Never mutate an object’s prototype after creation — it deopts inline caches. Set it via
Object.createorclass.” - “
Object.create(null)for untrusted maps to avoid prototype pollution;Object.hasOwnto check ownership.” - “
instanceofbreaks across realms — useArray.isArray/ the toString tag instead.”
Cross-references
- Classes (the sugar over this): Classes
thisresolution in methods: this and Binding- Prototype pollution as an attack: Frontend Security — Senior Interview Prep
- Backend contrast — Python MRO/descriptors: super() and MRO with multiple inheritance
Further reading
- MDN — Inheritance and the prototype chain: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Inheritance_and_the_prototype_chain
- MDN —
Object.create: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/create - V8 — Hidden classes / “Fast properties”: https://v8.dev/blog/fast-properties