Written by Rahul · Frontend Engineer at Google · Updated 2025
Here's the honest truth — I've interviewed over 200 frontend candidates, and maybe 10% actually understand prototypal inheritance. Most can say "objects inherit from other objects," but when you ask them to explain the prototype chain or why Object.create(null) is useful, they freeze. Let me break this down the way I wish someone had explained it to me.
What is Prototypal Inheritance?
In JavaScript, there are no traditional "classes" (even ES6 classes are syntactic sugar). Instead, objects can inherit directly from other objects through a hidden link called [[Prototype]].
const animal = {
eat() { console.log("Eating..."); },
sleep() { console.log("Sleeping..."); }
};
const dog = Object.create(animal);
dog.bark = function() { console.log("Woof!"); };
dog.bark(); // "Woof!" — own property
dog.eat(); // "Eating..." — inherited from animal
dog.sleep(); // "Sleeping..." — inherited from animalWhen you access dog.eat(), JavaScript first looks at dog itself. Not found? It follows the [[Prototype]] link to animal. Found it! This is the prototype chain.
The Prototype Chain — Visualized
dog → animal → Object.prototype → null
dog.bark() → found on dog ✅
dog.eat() → not on dog → found on animal ✅
dog.toString() → not on dog → not on animal → found on Object.prototype ✅
dog.fly() → not on dog → not on animal → not on Object.prototype → undefined ❌__proto__ vs prototype vs Object.getPrototypeOf()
This is where most people get confused. Let me clear it up:
| Term | What It Is | Use It? |
|---|---|---|
__proto__ | The actual link to the parent object. Exists on every object. | ❌ Never in production. It's deprecated. |
prototype | A property on functions only. Objects created with new Foo() get their [[Prototype]] set to Foo.prototype. | ✅ For constructor patterns |
Object.getPrototypeOf(obj) | The correct way to read an object's prototype. | ✅ Always use this |
function Person(name) {
this.name = name;
}
Person.prototype.greet = function() {
return `Hi, I am ${this.name}`;
};
const rahul = new Person("Rahul");
// These are the SAME object:
console.log(rahul.__proto__ === Person.prototype); // true
console.log(Object.getPrototypeOf(rahul) === Person.prototype); // trueES6 Classes — It's Still Prototypes Underneath
class Animal {
eat() { console.log("Eating"); }
}
class Dog extends Animal {
bark() { console.log("Woof"); }
}
const buddy = new Dog();
// Under the hood, this is identical to:
// Dog.prototype.__proto__ === Animal.prototype
console.log(Object.getPrototypeOf(Dog.prototype) === Animal.prototype); // trueClasses don't add a new inheritance model. They're syntactic sugar that makes the prototype pattern look like classical inheritance.
Real Production Use Cases
1. Polyfills
Every polyfill you've ever used works through prototypal inheritance:
2. Object.create(null) — Production Pattern at Google
We use Object.create(null) to create truly empty objects for hash maps:
3. Mixin Pattern
Production Issues I've Encountered
Issue 1: Prototype Pollution Attack
A developer accepted user input and used it as object keys without sanitization:
Issue 2: hasOwnProperty Confusion
Best Practices
- Prefer ES6 classes for readability, but understand prototypes underneath
- Use
Object.create(null)for dictionary/map objects to avoid prototype pollution - Never modify built-in prototypes (except for polyfills in controlled environments)
- Use
Object.hasOwn()instead ofinoperator when checking own properties - Freeze prototypes of sensitive objects:
Object.freeze(Object.prototype)in security-critical apps
Interview Tip
When asked "explain prototypal inheritance," draw the chain. Literally draw: instance → Constructor.prototype → Object.prototype → null. Then explain that property lookup walks this chain. Mention that ES6 classes are sugar. Mention Object.create(null). That's a senior-level answer.