Prototypes & Inheritance
If someone asks you "how does inheritance work in JavaScript?" and you jump straight to class extends, you've shown them you learned syntax, not the language.
The Prototype Chain
Every JavaScript object has an internal [[Prototype]] link to another object. When you access a property that doesn't exist on the object, the engine walks up this chain until it finds the property or hits null.
const animal = { eats: true };
const rabbit = Object.create(animal);
rabbit.jumps = true;
rabbit.jumps; // true â own property
rabbit.eats; // true â found on prototype
rabbit.toString(); // found on Object.prototypeThe chain: rabbit â animal â Object.prototype â null
Object.create vs Constructor Functions
Object.create sets the prototype directly â no constructor invocation, no new:
const proto = {
greet() {
return `Hello, ${this.name}`;
},
};
const user = Object.create(proto);
user.name = 'Alex';
user.greet(); // "Hello, Alex"Constructor functions do the same thing but with ceremony:
function User(name) {
this.name = name;
}
User.prototype.greet = function () {
return `Hello, ${this.name}`;
};
const user = new User('Alex');
// user.[[Prototype]] === User.prototypenew does four things: creates an empty object, sets its prototype to Constructor.prototype, binds this, and returns the object (unless the constructor returns an object explicitly).
Classes Are Syntactic Sugar
ES6 classes compile down to constructor functions and prototype assignment:
class Animal {
constructor(name) {
this.name = name;
}
speak() {
return `${this.name} makes a sound`;
}
}
// Equivalent to:
// Animal.prototype.speak = function() { ... }extends sets up a two-level prototype chain:
class Dog extends Animal {
bark() {
return `${this.name} barks`;
}
}
// Dog.prototype.[[Prototype]] === Animal.prototype
// Dog.[[Prototype]] === Animal (for static inheritance)Where Prototypes Bite You
Property shadowing
const parent = { items: [1, 2, 3] };
const child = Object.create(parent);
child.items.push(4);
parent.items; // [1, 2, 3, 4] â mutation, not shadowingReading child.items walks the chain and returns the same array reference. Mutation affects the prototype. To shadow, you'd need child.items = [...parent.items, 4].
instanceof checks the chain, not the constructor
const obj = Object.create(Array.prototype);
obj instanceof Array; // true â but obj is NOT an array
Array.isArray(obj); // falseinstanceof walks [[Prototype]] looking for Constructor.prototype. It can be fooled.
Interview Patterns
"Implement classical inheritance without class" â they want to see you wire up prototypes manually:
function Shape(x, y) {
this.x = x;
this.y = y;
}
Shape.prototype.move = function (dx, dy) {
this.x += dx;
this.y += dy;
};
function Circle(x, y, r) {
Shape.call(this, x, y); // super() equivalent
this.radius = r;
}
Circle.prototype = Object.create(Shape.prototype);
Circle.prototype.constructor = Circle;
Circle.prototype.area = function () {
return Math.PI * this.radius ** 2;
};"What's the difference between __proto__ and prototype?"
prototypeis a property on functions â it becomes the[[Prototype]]of instances created withnew.__proto__(deprecated, useObject.getPrototypeOf) is the accessor for an object's internal[[Prototype]]link.
Senior-Level Signal
In interviews, demonstrate that you understand prototypes as a delegation mechanism, not a copy mechanism. JavaScript doesn't copy methods from parent to child â it delegates lookups up the chain at runtime. This is fundamentally different from classical inheritance in Java or C++, and it's why mixins, composition, and Object.assign are natural patterns in JS.