Classical vs prototypal inheritance
The Short Answer
Classical inheritance (used in Java, C++, C#) is class-based — you define blueprints (classes) and create instances from them, with rigid hierarchies. Prototypal inheritance (JavaScript's native model) is object-based — objects inherit directly from other objects through a prototype chain, with no classes required. JavaScript's class syntax is syntactic sugar over prototypal inheritance — under the hood, it's still prototype chains, not true classical inheritance.
Classical Inheritance
In classical inheritance, classes are abstract blueprints that define structure and behavior. You create instances from classes, and classes can extend other classes to form hierarchies. The relationship is always class → instance, and inheritance is defined at class-definition time, not at runtime.
- Classes are blueprints — they don't hold data themselves
- Instances are created from classes via `new`
- Inheritance is defined statically (at compile/definition time)
- Hierarchies are rigid — changing a parent class affects all children
- Multiple inheritance is complex (diamond problem)
JavaScript's class keyword mimics this model syntactically, but the underlying mechanism is still prototypal. Here's what classical-style code looks like in JS — it works, but it's important to know what's happening underneath.
// Classical-style inheritance using ES6 class syntax
class Animal {
name: string;
constructor(name: string) {
this.name = name;
}
speak(): string {
return `${this.name} makes a sound.`;
}
}
class Dog extends Animal {
breed: string;
constructor(name: string, breed: string) {
super(name); // Call parent constructor
this.breed = breed;
}
speak(): string {
return `${this.name} barks.`; // Override parent method
}
fetch(): string {
return `${this.name} fetches the ball!`;
}
}
const rex = new Dog('Rex', 'Labrador');
rex.speak(); // "Rex barks." (own method)
rex.fetch(); // "Rex fetches the ball!" (own method)
// rex → Dog.prototype → Animal.prototype → Object.prototype
Prototypal Inheritance
In prototypal inheritance, there are no classes — only objects. An object can inherit from another object directly by having it as its prototype. When you access a property on an object, JavaScript walks up the prototype chain until it finds the property or reaches null. This is more flexible than classical inheritance because you can change prototypes at runtime and compose objects from multiple sources.
// Pure prototypal inheritance — objects inheriting from objects
const animal = {
speak() {
return `${this.name} makes a sound.`;
},
};
const dog = Object.create(animal); // dog's prototype is animal
dog.fetch = function () {
return `${this.name} fetches the ball!`;
};
const rex = Object.create(dog); // rex's prototype is dog
rex.name = 'Rex';
rex.breed = 'Labrador';
rex.speak(); // "Rex makes a sound." (found on animal via prototype chain)
rex.fetch(); // "Rex fetches the ball!" (found on dog)
// Prototype chain: rex → dog → animal → Object.prototype → null
console.log(Object.getPrototypeOf(rex) === dog); // true
console.log(Object.getPrototypeOf(dog) === animal); // true
No constructors, no new keyword, no class definitions — just objects linked to other objects. Object.create() is the purest expression of prototypal inheritance. The prototype chain is the lookup mechanism: if rex doesn't have speak, JavaScript checks dog, then animal, then Object.prototype.
Key Differences
| Classical | Prototypal | |
|---|---|---|
| Core unit | Class (blueprint) | Object (concrete thing) |
| Inheritance | Class extends class | Object links to object |
| Instance creation | new ClassName() | Object.create(proto) or factory functions |
| Hierarchy | Rigid, defined at design time | Flexible, can change at runtime |
| Method resolution | Defined by class hierarchy | Prototype chain lookup |
| Multiple inheritance | Complex (interfaces, mixins) | Natural (Object.assign, mixins) |
| JS implementation | class syntax (sugar) | Native mechanism (prototype chain) |
Factory Functions — The Prototypal Alternative
Factory functions are the prototypal alternative to classes. Instead of defining a class and using new, you write a function that creates and returns an object. Combined with Object.create() for shared methods, this gives you inheritance without the class ceremony — and makes composition easier than inheritance.
// Shared methods on a prototype object (not duplicated per instance)
const animalMethods = {
speak() {
return `${this.name} makes a sound.`;
},
};
function createAnimal(name: string) {
const animal = Object.create(animalMethods);
animal.name = name;
return animal;
}
// Composition over inheritance — mix behaviors
const canFetch = {
fetch() {
return `${this.name} fetches!`;
},
};
const canSwim = {
swim() {
return `${this.name} swims!`;
},
};
function createDog(name: string, breed: string) {
// Compose from multiple sources
const dog = Object.assign(
Object.create(animalMethods),
canFetch,
{ name, breed }
);
return dog;
}
const rex = createDog('Rex', 'Labrador');
rex.speak(); // From animalMethods prototype
rex.fetch(); // Mixed in from canFetch
This approach favors composition over inheritance — you pick and choose behaviors to mix into an object rather than inheriting everything from a rigid class hierarchy. It avoids the "gorilla-banana problem" where you want a banana but get the entire gorilla holding it and the jungle.
What JS Classes Really Are
JavaScript's class keyword doesn't introduce true classical inheritance — it's syntactic sugar over the existing prototype system. Understanding this is crucial because it explains behaviors that surprise developers coming from classical languages.
class Dog {
name: string;
constructor(name: string) {
this.name = name;
}
speak() {
return `${this.name} barks`;
}
}
// Under the hood, this is equivalent to:
function DogConstructor(this: any, name: string) {
this.name = name;
}
DogConstructor.prototype.speak = function () {
return `${this.name} barks`;
};
// Both create the same prototype chain:
const dog1 = new Dog('Rex');
const dog2 = new DogConstructor('Rex');
// dog1 → Dog.prototype → Object.prototype → null
// dog2 → DogConstructor.prototype → Object.prototype → null
console.log(typeof Dog); // "function" — classes are functions!
Why Interviewers Ask This
This question tests whether you understand JavaScript's object model at a fundamental level. Interviewers want to see that you know class is sugar over prototypes, can explain the prototype chain, understand the tradeoffs between inheritance and composition, and can articulate why JavaScript chose prototypal inheritance (flexibility, dynamic nature). It separates developers who just use classes from those who understand what's happening underneath.
Quick Revision Cheat Sheet
Classical: Class blueprints → instances, rigid hierarchies, static
Prototypal: Objects → objects, flexible chains, dynamic
JS class keyword: Syntactic sugar over prototype chain — not true classical
Object.create(): Pure prototypal inheritance — sets prototype directly
Composition: Object.assign() to mix behaviors — preferred over deep hierarchies
Prototype chain: obj → proto → proto → ... → null (property lookup path)