Skip to content

JavaScript Tutorial Classes and Object-Oriented JavaScript

Composition vs Inheritance

Lesson 195 of 258 2 min read By Deepak Paswan Updated Oct 4, 2026

There are two ways to share behaviour between objects.

  • Inheritance (“is a”): Dog extends Animal.
  • Composition (“has a” / “uses a”): an object contains other objects or functions and delegates work to them.

The problem with deep inheritance

Imagine game characters:

code
TEXT
Character
├── Fighter
│   └── MagicFighter     ← a fighter who also casts spells
└── Wizard
    └── ...              ← a wizard who also fights?

Soon you need something that does both, and the tree does not fit. Changes to a parent class also ripple into every child, which makes the code fragile.

Composition: assemble from small parts

Instead of asking “what is it?”, ask “what can it do?” and give each ability its own small piece.

script.js
JS
const canWalk = state => ({
  walk() { return `${state.name} walks`; }
});

const canSwim = state => ({
  swim() { return `${state.name} swims`; }
});

const canFly = state => ({
  fly() { return `${state.name} flies`; }
});

function createDuck(name) {
  const state = { name };
  return { ...canWalk(state), ...canSwim(state), ...canFly(state) };
}

function createPenguin(name) {
  const state = { name };
  return { ...canWalk(state), ...canSwim(state) };       // no flying
}

const duck = createDuck("Duck");
console.log(duck.fly());                                  // → Duck flies
console.log(typeof createPenguin("Pingu").fly);           // → undefined

Each ability is independent. A new combination is a new line, not a new class in a tree.

Composition with classes

An object can hold other objects.

script.js
JS
class Engine {
  start() { return "Engine started"; }
}

class Radio {
  play() { return "Music playing"; }
}

class Car {
  engine = new Engine();
  radio = new Radio();

  drive() {
    return `${this.engine.start()}, driving. ${this.radio.play()}`;
  }
}

console.log(new Car().drive()); // → Engine started, driving. Music playing

A Car has an Engine. It is not an engine.

Another practical case: components

User interface code is full of composition: a page contains a header, a list and a footer, and each is its own piece.

How to choose

SituationPrefer
A true “is a” relationship, a small stable hierarchyinheritance
Behaviour mixes in many combinationscomposition
You want to swap parts (different loggers, storage)composition
You are unsurestart with composition

A useful saying

> “Favor composition over inheritance.”

It is a guideline, not a rule. Inheritance is fine in small, clear cases (such as custom errors). The danger is deep, wide hierarchies built “just in case”.

Dependency injection

Composition makes testing easier: pass the parts in.

script.js
JS
class Notifier {
  constructor(sender) {
    this.sender = sender;          // any object with a send() method
  }
  notify(message) {
    return this.sender.send(message);
  }
}

const emailSender = { send: msg => `email: ${msg}` };
const smsSender = { send: msg => `sms: ${msg}` };

console.log(new Notifier(emailSender).notify("Hi")); // → email: Hi
console.log(new Notifier(smsSender).notify("Hi"));   // → sms: Hi

The Notifier does not care how messages are sent. That flexibility is the main benefit.