Back to JavaScript
JavaScript
easy
mid

What is the 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).

4 min read·~8 min to think through

Classical inheritance

java
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

js
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

js
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.
  • super calls.
  • Private fields (#x).

But the runtime model is unchanged.

Why the distinction matters

ClassicalPrototypal
Inheritance unitClassObject
When fixedCompile timeRuntime
Composition styleInheritance, mixins via interfacesObject composition, dynamic mixins
"Instance of"Type identityPrototype 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

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 .prototypeobj.__proto__ (or Object.getPrototypeOf(obj)) is the instance's prototype. Class.prototype is the prototype that new instances will get. Don't conflate.
  • Class fields are own properties, not prototype properties. x = 1 is added per instance; methods are on the prototype.
  • Arrow methods in class — bound per instance, slightly more memory.

Practical guidance

  • Use class for 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 setPrototypeOf at 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).

Senior engineer discussion

Seniors discuss why class is sugar, when classes are clearer than ad hoc objects, and avoid deep hierarchies. They explain composition patterns natural in prototypal but harder in classical.

Related questions