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

    Liskov Substitution

    Liskov Substitution demands that subtypes honor the base contract.

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

    Introduction

    Liskov Substitution demands that subtypes honor the base contract. If Square extends Rectangle but changing width also changes height, callers expecting Rectangle behavior break silently. LSP failures hide in callers — the subtype looks fine in isolation.

    The story

    A drawing app used Square extends Rectangle. It normalized widths with setWidth(10) on every shape. Squares changed height too, breaking layouts subtly. Fix: both implement Shape; neither extends the other.

    The business problem

    LSP violations cause silent production bugs:

    • Runtime surprises in callers using base-type references.
    • Tests that lie — subtypes tested directly, not through base contract.
    • False taxonomy — ISA logic without behavioral substitutability.
    • NotSupportedException in "implementations" of interface methods.

    The problem teams faced

    Red flags for LSP:

    • Subtypes throw on methods the base defines.
    • Subtypes weaken preconditions or strengthen postconditions.
    • Inheritance chosen for reuse, not true IS-A behavior.

    Understanding the topic

    LSP contract checklist for every subtype:

    • Honors all base methods without surprising exceptions.
    • Preserves invariants callers rely on.
    • Passes tests written against base-type references.

    Internal architecture

    Caller perspective:

    ts
    function layout(shapes: Rectangle[]) {
    shapes.forEach(s => s.setWidth(100)); // must not break any element
    }
    // Square in this list → LSP violation if width/height coupled

    Visual explanation

    LSP in three diagrams:

    Substitutability test
    Base contract
    Methods + invariants
    Subtype A
    Must pass
    Subtype B
    Must pass
    Caller tests
    Via base ref
    Test subtypes through base type — not in isolation.
    Rectangle / Square trap
    ISA logic
    Square IS-A Rectangle
    setWidth(10)
    Caller expectation
    Height changes
    LSP break
    Use Shape
    Composition fix
    ISA ≠ LSP. Behavioral substitutability decides inheritance.
    Fix path
    LSP fails
    Audit contract
    Not a subtype
    Honest naming
    Composition
    Has-a not is-a
    Contract tests
    Prevent regression
    When LSP fails, drop inheritance — don't patch exceptions.

    Informative example

    Read-only list LSP violation — common in legacy APIs:

    ts
    interface List<T> {
    add(item: T): void;
    get(i: number): T;
    }
    class ReadOnlyList<T> implements List<T> {
    add() { throw new Error("Not supported"); } // ❌ LSP violation
    }
    // ✅ Fix: separate interfaces
    interface ReadableList<T> { get(i: number): T; }
    interface WritableList<T> extends ReadableList<T> { add(item: T): void; }

    Execution workflow

    1LSP audit workflow
    1 / 4

    Document base contract

    Methods, pre/postconditions, invariants.

    Comments or contract tests.

    Real-world use

    Java Collections documents LSP-borderline types explicitly. Modern APIs prefer small interfaces (ISP + LSP together) over fat bases with throwing overrides.

    Production case study

    Rectangle/Square in drawing pipeline (above):

    • Symptom: intermittent layout bugs in production exports.
    • Fix: drop inheritance; shared Shape interface.
    • Lesson: ISA logic misleads — test substitutability.

    Trade-offs

    • Honest taxonomy prevents caller surprises.
    • Some "obvious" hierarchies must flatten.
    • Contract documentation adds upfront cost.

    Decision framework

    • Pause before every `extends` — run LSP checklist.
    • Prefer composition when behavioral overlap is partial.

    Best practices

    • Contract tests for public interfaces.
    • Throwing from override = red flag in review.

    Anti-patterns to avoid

    • Empty override that throws NotSupportedException.
    • Square extends Rectangle because geometry class said so.

    Common mistakes

    • Testing subtypes only directly — misses LSP breaks.

    Debugging tips

    • Bug only when passed as base type? Classic LSP — audit subtype contract.

    Optimization strategies

    • Sealed classes + pattern matching can replace fragile inheritance (Visitor alternative).

    Common misconceptions

    • LSP applies to interfaces too — any contract, not only inheritance.

    Advanced interview questions

    Interview Prep

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

    2 questions
    1IntermediateQuestionReal LSP violation?+

    Answer

    ReadOnlyList implementing full List; Stream subtypes changing sync expectations; Square/Rectangle.

    Follow-up

    Redesign one?
    2AdvancedQuestionEnforce LSP in review?+

    Answer

    Contract tests through base type — subtype-specific tests aren't enough.

    Follow-up

    Written contract tests?

    Summary

    You can audit LSP with contract tests, fix Square/Rectangle with composition, and catch throwing overrides in review.

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