Overview
Composition over inheritance means building behavior by combining objects (has-a) rather than extending classes (is-a). Instead of Duck extends FlyingAnimal, a Duck has a FlyBehavior. Behaviors can be mixed, swapped at runtime, and tested independently.
Inheritance creates a fixed, compile-time relationship and exposes parent internals; composition depends only on the component's public interface. Many design patterns (Strategy, Decorator, State, Bridge) are composition techniques. The rule is not 'never inherit' but 'inherit for true subtypes, compose for reuse'.
A molded toy car is one fixed shape. With LEGO, you snap on different wheels, engines, and bodies, and you can swap parts later without making a new toy.
When to use it
- Combinations of behaviors would create a class explosion (FlyingSwimmingQuackingDuck).
- Behavior should change at runtime.
- You want to reuse code without inheriting an entire parent API.
Where it shows up in interviews
Recognize it when: subclasses for every combination of features.
- Design a coffee machine with add-ons
- Design game characters with abilities
Recognize it when: behavior depends on configuration or state.
- Design a vending machine
- Design pricing rules
Where it is used in real software
The React team recommends composition (props, children, hooks) and has never recommended component inheritance hierarchies.
Unity and ECS architectures attach components (Collider, Renderer, Health) to entities instead of deep class trees.
BufferedReader wraps a Reader: behavior is composed, not inherited per combination.
Key terms
- Composition
- An object holds references to other objects that do work for it.
- Delegation
- Forwarding a call to a composed object.
- Has-a vs is-a
- Composition vs inheritance relationship.
- Class explosion
- Combinatorial growth of subclasses.
How it works, step by step
- 1Identify the varying behaviors
Flying, swimming, quacking.
- 2Define an interface per behavior
FlyBehavior, SoundBehavior.
- 3Implement behaviors separately
FlyWithWings, NoFly, Squeak.
- 4Inject behaviors into the object
Constructor or setter.
- 5Delegate calls
duck.fly() calls this.flyBehavior.fly().
Class explosion vs composition
Coffee with optional milk, sugar, and caramel
| Approach | Classes needed for 3 add-ons | Add a 4th add-on |
|---|---|---|
| Inheritance per combination | 2^3 = 8 subclasses | 16 subclasses |
| Composition (list of add-ons or decorators) | 1 Coffee + 3 add-on classes | +1 class |
NOWApproach: Inheritance per combination | Classes needed for 3 add-ons: 2^3 = 8 subclasses | Add a 4th add-on: 16 subclasses
Composition grows linearly with features; inheritance per combination grows exponentially.
Implementation
interface MoveBehavior { move(): string }interface AttackBehavior { attack(): string } const walk: MoveBehavior = { move: () => "walks" };const fly: MoveBehavior = { move: () => "flies" };const sword: AttackBehavior = { attack: () => "slashes with a sword" };const fireball: AttackBehavior = { attack: () => "casts a fireball" }; class Character { constructor(readonly name: string, private mover: MoveBehavior, private attacker: AttackBehavior) {} act() { return `${this.name} ${this.mover.move()} and ${this.attacker.attack()}`; } equip(attacker: AttackBehavior) { this.attacker = attacker; } // change at runtime} const knight = new Character("Knight", walk, sword);const dragon = new Character("Dragon", fly, fireball);knight.equip(fireball); // no new subclass neededComplexity and performance
vs O(2^n) with inheritance.
Negligible.
Trade-offs
Composition needs forwarding methods and wiring; inheritance gives reuse for free.
Behavior spread across collaborators can be harder to trace than one class.
Variants and related techniques
Classic patterns built on composition.
Language features for horizontal reuse (Scala traits, Kotlin delegation, TypeScript mixins).
Common mistakes
- Dogmatically avoiding all inheritance.
Fix: True subtype hierarchies (exceptions, sealed shapes) are fine.
- Exposing composed objects directly.
Fix: Delegate through meaningful methods to keep encapsulation.
Interview questions
Why prefer composition over inheritance?
Composition depends only on public interfaces, allows runtime changes, avoids class explosion and fragile base classes, and makes components independently testable.
Give an example where inheritance is still appropriate.
A true is-a hierarchy with a stable contract, such as exception types or a sealed set of shapes where every subtype honors the parent's behavior.
Practice problems
| Problem | Difficulty | What it trains |
|---|---|---|
| Refactor a Duck hierarchy with behaviors | Easy | Strategy composition. |
| Design a pizza builder with toppings | Medium | Avoid class explosion. |