Difference between Prototypal and Classical Inheritance in JavaScript
Classical (Java/C++): classes are blueprints; objects are instances. Inheritance is declarative + compile-time. Prototypal (JS): objects inherit from other **objects** via the [[Prototype]] chain — runtime-flexible. ES6 `class` is syntactic sugar over prototypes. In practice both look similar; the JS difference is that you can compose objects dynamically (`Object.create`, mixins, delegation).
Classical inheritance
class Animal { void speak() {} }
class Dog extends Animal { void speak() { System.out.println("woof"); } }
Dog d = new Dog();- Classes are templates.
- Hierarchy fixed at compile time.
- Methods looked up on the class definition.
Prototypal inheritance
const animal = { speak() { console.log("?"); } };
const dog = Object.create(animal);
dog.speak = () => console.log("woof");- Objects inherit from objects.
- Hierarchy can be rearranged at runtime (
Object.setPrototypeOf). - Property lookup walks the [[Prototype]] chain.
ES6 class as sugar
class Animal { speak() { console.log("?"); } }
class Dog extends Animal { speak() { console.log("woof"); } }
const d = new Dog();Under the hood this is still prototype-based: Dog.prototype.__proto__ === Animal.prototype. The class keyword adds:
- Strict mode body.
- Non-enumerable methods on prototype.
supercalls.- Private fields (
#x).
But the runtime model is unchanged.
Why the distinction matters
| Classical | Prototypal | |
|---|---|---|
| Inheritance unit | Class | Object |
| When fixed | Compile time | Runtime |
| Composition style | Inheritance, mixins via interfaces | Object composition, dynamic mixins |
| "Instance of" | Type identity | Prototype chain check |
JS instanceof walks the prototype chain — it's not a static type check. You can swap prototypes at runtime (though you shouldn't in hot code; it breaks JIT inline caches).
Composition over inheritance — natural in JS
const canSwim = { swim() { ... } };
const canFly = { fly() { ... } };
const duck = Object.assign({}, canSwim, canFly);Mixing capabilities without a hierarchy — easier to do in prototypal JS than in classical languages with single inheritance.
Common confusions
__proto__vs.prototype—obj.__proto__(orObject.getPrototypeOf(obj)) is the instance's prototype.Class.prototypeis the prototype that new instances will get. Don't conflate.- Class fields are own properties, not prototype properties.
x = 1is added per instance; methods are on the prototype. - Arrow methods in class — bound per instance, slightly more memory.
Practical guidance
- Use
classfor clarity when modeling a "kind" with shared methods (Component, EventEmitter). - Use plain objects + composition for mixed behavior and ad hoc shapes.
- Avoid deep inheritance hierarchies (the LSP / fragile-base-class problems hit both paradigms).
- Don't
setPrototypeOfat runtime — engine deoptimizes.
Interview framing
"Classical inheritance is class-based, compile-time, fixed hierarchy — Java/C++. Prototypal is object-based: every object has a [[Prototype]] link, and property lookup walks that chain. JS is fundamentally prototypal; the ES6 class keyword is syntactic sugar over the prototype model. Practically you can write either-flavored code in JS, but prototypal opens up composition patterns — mixins via Object.assign, runtime prototype swapping — that classical languages can't. instanceof walks the prototype chain, it's not a static type check. Avoid setPrototypeOf in hot code; the engine deoptimizes."
Follow-up questions
- •How does ES6 class differ from constructor functions?
- •What does Object.create do?
- •Compare composition vs inheritance.
Common mistakes
- •Confusing __proto__ with .prototype.
- •Deep inheritance hierarchies.
- •Mutating prototype on the fly in hot code.
Performance considerations
- •setPrototypeOf is slow — V8 deoptimizes shapes. Prefer constructing with the right prototype.
Edge cases
- •Object.create(null) for no-prototype objects.
- •class private fields (#x) — true encapsulation.
- •Mixins with super chains.
Real-world examples
- •React class components (rare now), EventEmitter, Node streams, DOM (Node → Element → HTMLElement chain).