What is the prototype chain in JavaScript?
Advertisement
🧩 Scenario
Architecture Walkthrough
Property Lookup Through the Chain
Every JavaScript object has an internal [[Prototype]] slot: a reference to another object or null. When you access a property on an object, the engine first checks the object's own properties. If the property is not found, it follows the [[Prototype]] link to the next object and checks there. This continues up the chain until a property is found or the chain reaches null, at which point the result is undefined.
Object.prototype sits at the top of the chain for all plain objects. It provides commonly available methods like toString, hasOwnProperty, and valueOf. Every object created with an object literal or Object.create inherits from Object.prototype unless explicitly set to inherit from null.
Object.create and Explicit Prototype Relationships
Object.create(proto) creates a new object whose [[Prototype]] is set to proto. This is the most direct way to set up an explicit inheritance relationship without using constructor functions or class syntax. The new object starts with no own properties; all methods are inherited from the prototype.
Object.getPrototypeOf(obj) is the standard way to read an object's prototype. Direct access via __proto__ works in most environments but is non-standard and should be avoided in production code. Object.hasOwn(obj, key) (or the older obj.hasOwnProperty(key)) checks whether a property exists directly on the object rather than somewhere in the chain.
class Syntax Is Syntactic Sugar
The class keyword introduced in ES6 does not create a new inheritance model. It is syntactic sugar over the existing prototype-based system. When you write class Dog extends Animal, JavaScript sets up Dog.prototype to have Animal.prototype as its [[Prototype]]. Instances of Dog follow the chain: instance → Dog.prototype → Animal.prototype → Object.prototype → null.
Every method defined in a class body is placed on the class's prototype object, not on each instance. This means all instances share the same method references, which is memory-efficient. Instance properties assigned in the constructor (using this.x = ... or class fields) are own properties on each instance.
Key Code Explained
// Explicit prototype chain with Object.create
const animal = {
breathe() {
return `${this.name} breathes`;
},
};
const dog = Object.create(animal); // dog.[[Prototype]] === animal
dog.name = 'Rex';
dog.bark = function () {
return 'Woof!';
};
console.log(dog.bark()); // 'Woof!' — own method
console.log(dog.breathe()); // 'Rex breathes' — found on animal via chain
console.log(dog.toString()); // found on Object.prototype
console.log(Object.getPrototypeOf(dog) === animal); // true
console.log(Object.getPrototypeOf(animal) === Object.prototype); // true
console.log(Object.getPrototypeOf(Object.prototype)); // null
// class syntax: same chain, different spelling
class Animal {
breathe() {
return `${this.name} breathes`;
}
}
class Dog extends Animal {
bark() {
return 'Woof!';
}
}
const rex = new Dog();
rex.name = 'Rex';
// rex → Dog.prototype → Animal.prototype → Object.prototype → null
console.log(Object.getPrototypeOf(rex) === Dog.prototype); // true
// Built-in methods live on prototypes, not instances
const arr = [1, 2, 3];
console.log(arr.hasOwnProperty('map')); // false
console.log('map' in arr); // true — found via chain
console.log(Array.prototype.hasOwnProperty('map')); // true
// for...in traverses the entire chain — can include inherited properties
const parent = { inherited: true };
const child = Object.create(parent);
child.own = true;
for (const key in child) {
console.log(key); // 'own', then 'inherited'
}
// Use Object.hasOwn to check only own properties
for (const key in child) {
if (Object.hasOwn(child, key)) console.log(key); // 'own' only
}
The for...in behavior is the most practical gotcha: it enumerates all enumerable properties across the entire chain. Modern code prefers Object.keys() (own enumerable string-keyed properties), Object.entries(), or for...of with Object.entries() to avoid this.
Tradeoffs
| Approach | Pro | Con |
|---|---|---|
| class extends | Readable, familiar for OOP developers, tooling-friendly | Hides prototype mechanics, makes engine behavior less obvious |
| Object.create | Explicit prototype control, no constructor complexity | Verbose for complex hierarchies, less common in modern code |
| Composition over inheritance | No prototype chain complexity, flexible | Requires more deliberate design upfront |
What Interviewers Actually Check
- Whether you can trace property lookup order along the chain including what happens at the end
- Whether you know
classsyntax is sugar over prototypes, not a different mechanism - Whether you know that built-in methods live on
Array.prototype, not on each array instance - Whether you know the danger of modifying built-in prototypes
- Whether you know
for...inincludes inherited properties and can name the alternative
Follow-Up Questions
- What is the difference between
Object.create(null)and{}as a starting point for a plain data object? - How does
instanceofcheck the prototype chain, and what are its limitations? - If you add a method to
Dog.prototypeafter creating an instance, can the instance call that method? - How does the prototype chain relate to memory usage when you have thousands of instances?
- What does it mean to "shadow" a prototype method, and when would you intentionally do it?
Common Candidate Mistakes
- Using
__proto__to inspect or set the prototype instead ofObject.getPrototypeOfandObject.setPrototypeOf - Not knowing that
for...inincludes inherited enumerable properties, leading to unexpected key enumeration - Believing
classcreates a fundamentally different inheritance model when it is purely syntactic sugar - Modifying
Array.prototypeorObject.prototypein application code and not understanding why it is dangerous - Not knowing the difference between own properties and inherited properties or how to check for each
Interview Readiness Checklist
Before you leave this question, make sure you can answer:
- Can you describe property lookup order along the prototype chain ending at
null? - Can you use
Object.createto set up an explicit prototype relationship withoutclasssyntax? - Can you draw the prototype chain for a class that
extendsanother class? - Can you explain why all arrays share
.mapwithout it being duplicated on each instance? - Can you describe what
for...inincludes and how to restrict enumeration to own properties?
Summary
Every JavaScript object has an internal [[Prototype]] link to another object or null. When a property is accessed, the engine walks this chain from the object outward until it finds the property or reaches null. This prototype chain is the mechanism behind all JavaScript inheritance.
The class keyword is syntactic sugar over this mechanism. Class methods are placed on the class's prototype, and extends wires SubClass.prototype to inherit from SuperClass.prototype. No new inheritance model is introduced; the prototype chain operates exactly as it does with Object.create.
The practical gotchas are for...in looping over inherited properties and the danger of modifying built-in prototypes like Array.prototype. Modern code uses Object.keys(), Object.hasOwn(), and for...of to work around these edge cases cleanly.
Do arrow functions have prototypes?
No, arrow functions do not have a prototype property and cannot be used as constructors.
Advertisement
Stay Updated
Get the latest frontend challenges, interview questions and tutorials delivered to your inbox.
Advertisement