Skip to content

Liskov Substitution Principle (LSP)

When to Use

When designing inheritance hierarchies, implementing interfaces, or using polymorphism. LSP ensures subtypes don't break expectations set by base types.

Decision: What LSP Means

Barbara Liskov's Definition (1994): If S is a subtype of T, then objects of type T may be replaced with objects of type S without altering the correctness of the program.

In practice: Subtypes must honor the behavioral contract of the base type, not just the method signature.

Base type expects... Subtype must... Violation example
Method never throws exception X Not throw exception X Rectangle.setWidth works, Square.setWidth throws
Method accepts null input Accept null input Base allows null, subtype rejects it
Method returns non-empty list Return non-empty list Subtype returns empty list
Method completes in O(1) Not degrade to O(n) Performance contract violation

Pattern: LSP Violation (Rectangle/Square)

Classic Violation (Java/TypeScript):

class Rectangle {
  constructor(protected width: number, protected height: number) {}

  setWidth(w: number) { this.width = w; }
  setHeight(h: number) { this.height = h; }

  area(): number { return this.width * this.height; }
}

class Square extends Rectangle {
  setWidth(w: number) {
    this.width = w;
    this.height = w; // Breaks LSP: unexpected side effect
  }

  setHeight(h: number) {
    this.width = h;
    this.height = h; // Breaks LSP: unexpected side effect
  }
}

// Client code breaks with Square
function resizeRectangle(rect: Rectangle) {
  rect.setWidth(5);
  rect.setHeight(10);
  expect(rect.area()).toBe(50); // Fails for Square (100 instead)
}

LSP-Compliant:

interface Shape {
  area(): number;
}

class Rectangle implements Shape {
  constructor(private width: number, private height: number) {}
  area(): number { return this.width * this.height; }
}

class Square implements Shape {
  constructor(private side: number) {}
  area(): number { return this.side * this.side; }
}

// No inheritance, no LSP violation

Pattern: LSP Violation (Bird/Penguin)

Python Violation:

class Bird:
    def fly(self):
        return "Flying!"

class Penguin(Bird):
    def fly(self):
        raise NotImplementedError("Penguins can't fly")
        # Breaks LSP: client expects fly() to work

LSP-Compliant:

class Bird:
    def move(self):
        return "Moving"

class FlyingBird(Bird):
    def fly(self):
        return "Flying!"

class Penguin(Bird):
    def swim(self):
        return "Swimming!"

# Penguin is a Bird, but not a FlyingBird

Behavioral Subtyping Rules

Preconditions: Subtype cannot strengthen (require more) Postconditions: Subtype cannot weaken (guarantee less) Invariants: Subtype must preserve base type invariants Exceptions: Subtype cannot throw new checked exceptions

Common Mistakes

  • Using inheritance for code reuse -- Inheritance is "is-a", not "has-a". Why: LSP violations when subtype isn't truly substitutable
  • Strengthening preconditions -- Base allows null, subtype rejects null. Why: clients passing null to base type break with subtype
  • Weakening postconditions -- Base guarantees non-null return, subtype returns null. Why: clients expecting non-null crash
  • Throwing unexpected exceptions -- Subtype throws exceptions base doesn't. Why: breaks client exception handling
  • Empty method implementations -- Subtype implements required method as no-op. Why: violates behavioral contract (NotImplementedError pattern)
  • Performance degradation -- Base is O(1), subtype is O(n). Why: violates implicit performance contract