JavaScriptMedium

Classical vs prototypal inheritance

01

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.

02

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.tstypescript
// 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
03

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.

prototypal.tstypescript
// 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.

04

Key Differences

ClassicalPrototypal
Core unitClass (blueprint)Object (concrete thing)
InheritanceClass extends classObject links to object
Instance creationnew ClassName()Object.create(proto) or factory functions
HierarchyRigid, defined at design timeFlexible, can change at runtime
Method resolutionDefined by class hierarchyPrototype chain lookup
Multiple inheritanceComplex (interfaces, mixins)Natural (Object.assign, mixins)
JS implementationclass syntax (sugar)Native mechanism (prototype chain)
05

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.

factory-functions.tstypescript
// 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.

06

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-is-sugar.tstypescript
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!
07

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)