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.
Interview Q&A
Q: What is the prototype chain?
A: 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 };
const dog = Object.create(animal); // dog.[[Prototype]] === 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).
Q: __proto__ vs prototype vs [[Prototype]]?
A:
[[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; // true
Q: How does class map onto prototypes?
A: 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();
a.bark === b.bark; // true — same function on the prototype
extends sets Sub.prototype.[[Prototype]] = Base.prototype (instance chain) and Sub.[[Prototype]] = Base (static inheritance).
Q: Why is Object.setPrototypeOf (and __proto__=) a performance trap?
A: 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.
Q: How do you check whether a property is own vs inherited?
A:
Object.hasOwn(obj, "x"); // preferred (ES2022)
Object.prototype.hasOwnProperty.call(obj, "x"); // pre-2022, safe even if obj has its own `hasOwnProperty`
"x" in obj; // true for inherited too
for...in iterates inherited enumerable keys; Object.keys returns only own enumerable keys. Prefer Object.keys/entries to avoid surprises.
Q: How do you create an object with no prototype, and why?
A: 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.
Q: instanceof — how does it work, and when does it lie?
A: 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 ../17_security/. - 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
- “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.md
thisresolution in methods: this-and-binding.md- Prototype pollution as an attack: ../17_security/
- Backend contrast — Python MRO/descriptors: ../../backend/02_python_core/tricky_questions/10_super_mro_diamond.md
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