Skip to content

Composition Over Inheritance

When one object needs the abilities of another, you have two broad choices. Inheritance says “a FlyingDuck is a Duck” and pulls in the parent’s behaviour automatically. Composition says “a Duck has a flying behaviour” and holds a reference to a separate object that supplies it. Both reuse code, but they age very differently as requirements shift.

Inheritance binds a subclass to its parent at compile time. That is rigid in three ways. First, a subclass inherits everything, including behaviour it does not want — so a RubberDuck that cannot fly still inherits fly(). Second, behaviour is fixed for the object’s lifetime; you cannot change how a duck flies at runtime. Third, deep hierarchies couple unrelated features: when you need every combination of fly-or-not and quack-or-not, a tree of subclasses explodes combinatorially, and a change near the root ripples to every leaf.

Composition replaces the tree with a small set of interchangeable parts. A Duck holds a FlyBehavior and a QuackBehavior; each is an interface with a few concrete implementations. New combinations cost nothing — you just plug different parts together — and because the parts are held as fields, you can even swap them at runtime. The example below refactors a Duck that hard-codes flying via inheritance into one that delegates to an injected FlyBehavior.

// Composition: the duck delegates flying to an injected behaviour.
interface FlyBehavior {
fly(): string;
}
class FlyWithWings implements FlyBehavior {
fly(): string {
return 'flying with wings';
}
}
class NoFly implements FlyBehavior {
fly(): string {
return 'cannot fly';
}
}
class Duck {
constructor(private behavior: FlyBehavior) {}
performFly(): string {
return this.behavior.fly();
}
setFly(behavior: FlyBehavior): void {
this.behavior = behavior; // swap behaviour at runtime
}
}
const rubber = new Duck(new NoFly());
console.log(rubber.performFly()); // cannot fly
rubber.setFly(new FlyWithWings());
console.log(rubber.performFly()); // flying with wings

On the left, a rigid hierarchy needs a new subclass for every combination of abilities. On the right, the duck holds a behaviour it can swap.

classDiagram
  class Duck
  class FlyingDuck
  class NonFlyingDuck
  Duck <|-- FlyingDuck
  Duck <|-- NonFlyingDuck
  class FlexibleDuck
  class FlyBehavior {
    <<interface>>
    +fly()
  }
  FlexibleDuck --> FlyBehavior : has-a, swappable
Inheritance fixes behaviour in a tree; composition holds a swappable part

This “favour composition over inheritance” guideline is the backbone of many patterns: Strategy, Decorator, and State all replace a subclass with a held, interchangeable object. It does not ban inheritance — a genuine is-a relationship with a stable base is still the right tool. The lesson is to reach for composition first and let inheritance prove it is needed.

What is the core difference between inheritance and composition?
Why do deep inheritance hierarchies become rigid?
In the refactored example, how does the duck change how it flies?
Which patterns are built on favouring composition over inheritance?