Design Patterns Tutorial 0/70 lessons ~6 min read Lesson 15

    Composition Over Inheritance

    Composition over inheritance is the modern default.

    Course progress0%
    Focus
    21 guided sections
    Practice signal
    Examples included
    Career prep
    Interview Q&A included

    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:

    text
    Inheritance: Composition:
    FlyingDragon Character
    extends Dragon ├── movement: Movable
    extends Creature ├── combat: Damageable
    └── render: Renderable

    Visual explanation

    Composition in three diagrams:

    Inheritance vs composition
    extends Dragon
    14 levels
    Character
    Holder
    + Flyable
    Component
    + Damageable
    Component
    New behavior = new component, not new branch in tree.
    When inheritance still fits
    Fixed algorithm
    Template Method
    Stable framework
    JPA base entity
    Few subclasses
    Documented contract
    Else compose
    Default rule
    Inheritance for stable closed algorithms — composition for evolving features.
    React Hooks = composition
    Component
    Function
    useAuth
    Behavior hook
    useFetch
    Behavior hook
    Compose UI
    No class tree
    Modern UI abandoned deep class hierarchies for hook composition.

    Informative example

    Game character composition:

    ts
    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 edit
    class FlyingMovement implements Movable {
    move(dx, dy) { /* air physics */ }
    }

    Execution workflow

    1Refactor inheritance to composition
    1 / 4

    Identify reused behavior

    Extract to component class/module.

    Not taxonomy — capability.

    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.

    2 questions
    1IntermediateQuestionWhen is inheritance right?+

    Answer

    Template Method, stable framework bases (JPA Entity, abstract Spring config) with closed algorithm and few subclasses.

    Follow-up

    Show Template Method.
    2AdvancedQuestionFragile base class?+

    Answer

    Changing parent breaks subclasses in ways parent tests miss. Composition decouples reuse from parent refactor risk.

    Follow-up

    How libraries mitigate?

    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.

    Ready to mark this lesson complete?Track your journey across the entire course.