Composition Over Inheritance
Composition over inheritance is the modern default.
Introduction
Composition over inheritance is the modern default. Inheritance was the original OOP hammer; fragile base classes and LSP traps taught the industry that composition + interfaces deliver the same polymorphism with less coupling. React Hooks, Go struct embedding, and ECS game engines are composition taken seriously.
The story
A game engine had fourteen inheritance levels by year five. Adding "flying" required editing eight ancestors. Composition refactor — Character holds Movable, Damageable, Renderable components — added flying by attaching a component, zero inheritance edits.
The business problem
Deep inheritance hurts change velocity:
- Fragile base class — parent refactor breaks unknown subclasses.
- Taxonomy traps — Robot extends Animal because reuse needed one method.
- Ripple edits — new behavior edits ancestors, not adds components.
- LSP landmines — ISA hierarchies that behave differently.
The problem teams faced
Inheritance-first design symptoms:
- >3 levels of extends without stable framework contract.
- Subclass explosion for feature combinations (FlyingSwimmingDragon).
- Reuse via extends when has-a would suffice.
Understanding the topic
Has-a beats is-a for most behavior reuse:
- Holder — class/module owning behavior components.
- Components — small capability providers (Movable, Notifier).
- Interfaces — optional contracts components implement.
Internal architecture
Structure comparison:
Inheritance: Composition:FlyingDragon Characterextends Dragon ├── movement: Movableextends Creature ├── combat: Damageable└── render: Renderable
Visual explanation
Composition in three diagrams:
Informative example
Game character composition:
interface Movable { move(dx: number, dy: number): void; }interface Damageable { takeDamage(n: number): void; }class Character {constructor(private movement: Movable,private combat: Damageable,) {}move(dx: number, dy: number) { this.movement.move(dx, dy); }hit(n: number) { this.combat.takeDamage(n); }}// Add flying = swap Movable implementation — no inheritance editclass FlyingMovement implements Movable {move(dx, dy) { /* air physics */ }}
Execution workflow
Identify reused behavior
Extract to component class/module.
Real-world use
Go forbids inheritance — struct embedding + interfaces only. Unity/Unreal ECS is extreme composition. React Hooks replaced class hierarchies. Strategy, Decorator, Composite, Mediator are composition-native patterns.
Production case study
Game engine hierarchy refactor (above): new behaviors in hours not days; fourteen levels flattened to component graph.
Trade-offs
- Flexible behavior assembly; avoids LSP/fragile base.
- More wiring; verbose without DI at scale.
- Template Method genuinely wants inheritance sometimes.
Decision framework
- Default to composition for new behavior reuse.
- Inheritance for Template Method and stable framework bases only.
- Flatten when inheritance depth exceeds three without framework reason.
Best practices
- Favor object composition in GoF patterns — most are composition-based.
- TypeScript mixins for shared behavior without extends.
- ECS for games/simulations with combinatorial features.
Anti-patterns to avoid
- Subclass explosion: FlyingSwimmingFireDragon extends ...
- Extends only to call super.oneMethod().
Common mistakes
- Composition without interfaces — hard to test swap components.
Debugging tips
- Behavior missing after refactor? Trace delegation chain on holder.
Optimization strategies
- Document component contracts — same as ISP role interfaces.
Common misconceptions
- Composition ≠ no polymorphism. Interfaces + delegation = same substitutability.
Advanced interview questions
Interview Prep
Practice concise answers, then expand each card for the explanation.
1IntermediateQuestionWhen is inheritance right?+
Answer
Follow-up
2AdvancedQuestionFragile base class?+
Answer
Follow-up
Summary
You can choose composition over inheritance, know when Template Method still fits, and refactor deep trees into components. Tell the game engine story — you're ready for the GoF catalog modules.