Low-Level Design Tutorial 0/42 lessons ~6 min read Lesson 10

    Composition vs Inheritance

    Composition builds objects by combining other objects (has-a).

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

    Introduction

    Composition builds objects by combining other objects (has-a). Inheritance specializes types (is-a). The Gang of Four advice — favor composition over inheritance — appears in nearly every LLD interview because composition reduces coupling and fragile base classes.

    Example: an Elevator has a Motor and Door, it is not a Motor. A Car is a Vehicle — inheritance fits. Choosing wrong damages extensibility.

    This lesson gives decision rules and refactoring paths when you inherit too eagerly.

    Understanding the topic

    Key concepts

    • Composition: delegate behavior to composed objects.
    • Inheritance: subtype polymorphism with shared interface.
    • Composition avoids exposing superclass internals.
    • Inheritance ties you to parent implementation changes.
    • Delegation pattern: outer object forwards calls to inner.
    • Mix: implement interface + compose helper (e.g., List decorator).
    text
    flowchart TB
    Encapsulation --> Abstraction
    Abstraction --> Inheritance
    Inheritance --> Polymorphism

    Step-by-step explanation

    1. Ask: is it truly a subtype? If no, compose.
    2. Identify behaviors to reuse — extract to separate class, inject.
    3. Expose composed object only through outer class methods.
    4. Use interface on outer class matching client needs.
    5. Replace inheritance with composition when subclass overrides most methods.
    6. Document owned lifecycle — outer creates/destroys components.

    Informative example

    Car composes Engine — does not extend Engine:

    java
    public final class Engine {
    public void start() { /* ... */ }
    public void stop() { /* ... */ }
    }
    public final class Car {
    private final Engine engine;
    private final String vin;
    public Car(String vin, Engine engine) {
    this.vin = vin;
    this.engine = engine;
    }
    public void startTrip() {
    engine.start();
    }
    public void endTrip() {
    engine.stop();
    }
    }

    If ElectricEngine and GasEngine differ, inject Engine interface implementations — Car stays stable.

    Real-world use

    Real-world applications

    • Replacing instanceof stacks with composed strategies.
    • Building rich domain objects from value components.
    • Testability — mock composed collaborators.

    Best practices

    • Default to composition for reuse of behavior.
    • Use inheritance when LSP-safe substitution is required.
    • Keep composed objects private.
    • Inject interfaces for composed parts.
    • Avoid multi-level inheritance; prefer flat composition trees.

    Common mistakes

    • Subclassing HashMap to add logging (fragile — use wrapper).
    • Inheriting utility class for random helper methods.
    • Exposing composed objects for direct mutation.
    • Deep inheritance where strategy object would suffice.

    Advanced interview questions

    Interview Prep

    Practice concise answers, then expand each card for the explanation.

    5 questions
    1BeginnerQuestionWhat does 'favor composition over inheritance' mean?+

    Answer

    Reuse behavior by containing objects rather than subclassing unless IS-A is clear.
    2BeginnerQuestionGive an example of composition.+

    Answer

    A Car has an Engine; Car delegates start() to engine.start().
    3IntermediateQuestionWhen is inheritance still correct?+

    Answer

    True domain subtypes: SavingsAccount extends Account with substitutable behavior.
    4IntermediateQuestionHow does composition support testing?+

    Answer

    Inject mock collaborators without subclassing the system under test.
    5AdvancedQuestionRefactor inherited Stack with push logging to composition.+

    Answer

    Wrap Stack in LoggingStack delegating to inner Stack — no fragile extends Stack.

    Summary

    Composition = has-a; inheritance = is-a. Default to composition for behavior reuse. Inheritance when subtyping and LSP apply. Delegation keeps outer API stable. Composition simplifies testing and extension.

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