<arch.design/>
Principles/Composition over Inheritance
{ }CodeArchitectureintermediate1994oopgofhas-ais-a

Composition over Inheritance

Build complex behaviour by combining small, focused objects rather than extending a class hierarchy — keeping coupling low and flexibility high.

5/5
{ }
Operates at: Code level

Inside a codebase — classes, modules, files

How it works

The GoF book (1994) coined the phrase 'favor object composition over class inheritance' after observing that deep inheritance hierarchies are one of the most common sources of fragile code.

Inheritance (IS-A) creates a compile-time dependency between a subclass and its parent. Any change to the base class — a new field, a changed method signature, altered behaviour — ripples to every subclass. The Fragile Base Class problem is real: a 'harmless' parent change can silently break subtypes.

Composition (HAS-A) avoids this by assembling behaviour from injected collaborators. A Duck doesn't extend Animal and FlyingThing — it holds a MoveBehavior and a FlyBehavior. Behaviours are interfaces; concrete classes are injected. Swapping the fly behaviour at runtime doesn't require a new subclass.

Inheritance is still appropriate for true IS-A relationships with stable base classes. The advice is to reach for composition *first*, and only use inheritance when the hierarchy is genuinely stable and the IS-A relationship is real.

Implementation

TypeScript · Go · Rust
// ❌ Inheritance — fragile hierarchy breaks when new combinations arrive
class Animal { move() { return "moves"; } }
class Dog extends Animal { bark() { return "woof"; } }
// What about a dog that can swim AND herd? Hierarchy explodes.

// ✓ Composition — assemble behaviours as interfaces
interface Movable  { move(): string; }
interface Swimmable { swim(): string; }
interface Herder   { herd(): string; }

const walkBehaviour: Movable   = { move: () => "walks on four legs" };
const swimBehaviour: Swimmable = { swim: () => "paddles through water" };
const herdBehaviour: Herder    = { herd: () => "circles the flock" };

class BorderCollie {
  constructor(
    private movement: Movable,
    private swimming: Swimmable,
    private herding: Herder,
  ) {}
  move()  { return this.movement.move(); }
  swim()  { return this.swimming.swim(); }
  herd()  { return this.herding.herd(); }
}

const dog = new BorderCollie(walkBehaviour, swimBehaviour, herdBehaviour);
// Behaviours are independently replaceable and testable

Why it matters

Inheritance couples a subclass to its parent's internals. As hierarchies deepen, they become rigid — changing anything near the root breaks the tree. Composition keeps each piece independent and replaceable, making systems easier to test and evolve.

When to use

  • When behaviour needs to vary at runtime (inject different strategies)
  • When base classes are unstable or owned by a third party
  • Mixing several unrelated capabilities into one class
  • Prefer composition by default — use inheritance only when you have a genuine IS-A relationship

When NOT to use

  • Framework classes that explicitly define an extension point (React.Component, JUnit test cases)
  • True IS-A hierarchies with stable, well-understood base classes

Trade-offs

+

Behaviours are independently replaceable and testable

More wiring code — you must assemble the collaborators explicitly

+

No fragile base class problem — changes stay local

Too many tiny collaborators can scatter logic across many files

+

Enables runtime behaviour changes without new subclasses

Interface explosion if every behaviour gets its own interface prematurely

In production

React (hooks)

Hooks replaced class inheritance for shared logic — compose useState, useEffect, custom hooks instead of extending Component

Go (embedding)

Go has no inheritance; struct embedding plus interface composition is the idiomatic way to reuse and extend behaviour

Rust (traits)

Traits composed on structs — no class hierarchy; multiple traits mix freely without diamond-inheritance problems

Industry adoption

5/5Ubiquitous — used at virtually every scale-focused company.

Related principles