Liskov Substitution Principle

Subtypes must be substitutable for base types

Overview

The Liskov Substitution Principle (LSP) states that objects of a superclass should be replaceable with objects of a subclass without affecting the correctness of the program. If S is a subtype of T, then objects of type T can be replaced with objects of type S.

Violations of LSP often indicate that inheritance is being misused.

Key Concepts

Subtypes must be substitutable for their base types

Child classes should not break parent class behavior

Preconditions cannot be strengthened in subtypes

Postconditions cannot be weakened in subtypes

Often violated when using inheritance for code reuse only

Code Example

java
1// ❌ BAD: Violates LSP - Square is not substitutable for Rectangle
2class Rectangle {
3    protected int width;
4    protected int height;
5    
6    public void setWidth(int width) {
7        this.width = width;
8    }
9    
10    public void setHeight(int height) {
11        this.height = height;
12    }
13    
14    public int getArea() {
15        return width * height;
16    }
17}
18
19class Square extends Rectangle {
20    @Override
21    public void setWidth(int width) {
22        this.width = width;
23        this.height = width;  // Violates LSP!
24    }
25    
26    @Override
27    public void setHeight(int height) {
28        this.width = height;  // Violates LSP!
29        this.height = height;
30    }
31}
32
33// This code breaks with Square
34void testRectangle(Rectangle rect) {
35    rect.setWidth(5);
36    rect.setHeight(4);
37    assert rect.getArea() == 20;  // Fails for Square! Area would be 16
38}
39
40// ✅ GOOD: Proper abstraction respecting LSP
41interface Shape {
42    int getArea();
43}
44
45class Rectangle implements Shape {
46    private final int width;
47    private final int height;
48    
49    public Rectangle(int width, int height) {
50        this.width = width;
51        this.height = height;
52    }
53    
54    @Override
55    public int getArea() {
56        return width * height;
57    }
58}
59
60class Square implements Shape {
61    private final int side;
62    
63    public Square(int side) {
64        this.side = side;
65    }
66    
67    @Override
68    public int getArea() {
69        return side * side;
70    }
71}
72
73// Now both work correctly through Shape interface
74void testShape(Shape shape) {
75    System.out.println("Area: " + shape.getArea());  // Works for both!
76}

Square changing both dimensions when one is set breaks Rectangle behavior. Making them separate Shape implementations fixes this.

Real-World Example

  • Bird → Penguin: If Bird has fly(), Penguin can't properly inherit (penguins don't fly)
  • Vehicle → Boat: If Vehicle has changeGear(), boats might not have gears
  • File → ReadOnlyFile: If File has write(), ReadOnlyFile can't truly substitute

Best Practices

  • 1.

    Use inheritance only for true "is-a" relationships

  • 2.

    Ensure child classes don't weaken parent behavior

  • 3.

    Don't throw unexpected exceptions in overridden methods

  • 4.

    Consider composition if LSP is difficult to maintain

  • 5.

    Test substitutability with parent class tests

💡 Interview Tips

  • Use Rectangle/Square as classic example

  • Explain why it's about behavioral substitutability

  • Discuss design by contract (pre/postconditions)

  • Show how to fix violations with composition or interfaces

  • Connect to proper use of inheritance

AI Tutor

Ask about the topic

Sign in Required

Please sign in to use the AI tutor

Sign In
Liskov Substitution Principle - SOLID Principles | LLD | Revise Algo