Detail chart · Frontend

Composition Over Inheritance

Principle ◆◆◇◇◇

Favour assembling behaviour from composable units rather than deep inheritance chains that couple consumers to implementation details.

Summary

Composition Over Inheritance is a design principle that favors assembling behavior from small, independent components over building deep inheritance hierarchies. Inherited behavior is implicit and hard to change; composed behavior is explicit, targeted, and independently testable.

Problem

Deep inheritance chains couple subclasses to the internal structure of parent classes. Changes to base classes ripple through the entire hierarchy. Behavior cannot be mixed and matched across subtypes without violating the hierarchy.

Solution

Delegate behavior to injected or composed collaborators rather than inheriting it.

// Inheritance — rigid, coupled
class FlyingFishAnimal extends SwimmingAnimal {
  // forced to pick one base; flying behavior must be duplicated or hacked in
}

// Composition — flexible, explicit
interface Swimmer { swim(): void; }
interface Flier   { fly(): void; }

class Fish implements Swimmer {
  swim() { console.log("swimming"); }
}

class FlyingFish {
  constructor(
    private swimmer: Swimmer,
    private flier: Flier
  ) {}

  swim() { this.swimmer.swim(); }
  fly()  { this.flier.fly(); }
}

Each capability is a focused, independently testable unit. FlyingFish can combine them without inheriting from either.

The same shape holds in Python, using composition instead of a multiple-inheritance chain:

# Inheritance — rigid, coupled
class FlyingFishAnimal(SwimmingAnimal):
    # forced to pick one base; flying behavior must be duplicated or hacked in
    pass

# Composition — flexible, explicit
class Swimmer:
    def swim(self) -> None:
        print("swimming")

class Flier:
    def fly(self) -> None:
        print("flying")

class FlyingFish:
    def __init__(self, swimmer: Swimmer, flier: Flier):
        self._swimmer = swimmer
        self._flier = flier

    def swim(self) -> None:
        self._swimmer.swim()

    def fly(self) -> None:
        self._flier.fly()

# FlyingFish combines both capabilities without extending either class
flying_fish = FlyingFish(Swimmer(), Flier())

Python’s multiple inheritance can approximate composition via mixins, but it reintroduces the fragile base class problem the moment two mixins share a method name. Explicit composition avoids the ambiguity entirely — FlyingFish owns its collaborators rather than merging their namespaces.

Live Playground

Experiment with the pattern below. In the before version, FlyingFish can extend only one base class, so it extends SwimmingAnimal and carries a copy of fly(). When a bug fix caps flight altitude in FlyingAnimal, the copy doesn’t get it. Penguin extends FlyingAnimal because a penguin is a bird, so it has to override fly() to throw, and a loop over every FlyingAnimal crashes on it. In the after version, swimming and flying are small collaborators that each animal composes as it needs. The altitude fix lives in one Flying class and reaches every animal that uses it. Penguin never gets a fly() method, so the compiler keeps it out of the list of fliers.

When to Use

Avoid when:

Trade-offs

BenefitCost
Behavior is explicit — you can see exactly what a class doesMore wiring and delegation boilerplate
Capabilities are independently testable and reusableCan require more files and interfaces
No fragile base class problem — changes are localizedRequires discipline to avoid deep composition chains
Runtime behavior can be varied without subclassingDiscoverability can be harder than method inheritance