Liskov Substitution Principle

Difficulty: Intermediate

Question

Explain the Liskov Substitution Principle. Why is Square extending Rectangle a classic violation?

Answer

The Liskov Substitution Principle, the L in SOLID, is the one most students find abstract, so let me explain it with a plain-language test and then the famous example.

The principle says that if S is a subtype of T, then objects of type T should be replaceable with objects of type S without breaking the correctness of the program. In everyday words: a subclass should be usable anywhere its parent is expected, and the calling code should not need to know or care which one it got. If you have to write if (shape instanceof Square) to avoid a surprise, LSP has been violated.

The key insight is that inheritance is about behaviour, not about how things look in the real world. In geometry a square is a rectangle. But in code, a mutable Rectangle has an implied contract: setting the width does not change the height. Client code can rely on that. When Square extends Rectangle and overrides setWidth to also change height, it breaks the contract. The resize method that sets width to 5 and height to 4 expects an area of 20. With a Rectangle it prints 20, but with a Square it prints 16, because the second setter overwrote both sides. The subclass is not substitutable, even though it compiles perfectly.

How do you formalise this? Behavioural subtyping rules from Barbara Liskov say: preconditions cannot be strengthened in a subclass (do not demand more from callers than the parent did), postconditions cannot be weakened (do not promise less), invariants of the parent must be preserved, and the subclass should not throw new unexpected exceptions. Java also enforces some of this in the type system through covariant returns, no narrower access, and no broader checked exceptions, but the behavioural rules are not compiler-checked.

Other typical violations. A Bird class with a fly() method and a Penguin subclass that throws UnsupportedOperationException. An ArrayList-style read-only list that throws when add is called, which is why Java's unmodifiable collections are a debated example. A Stack extending Vector. A subclass that does nothing in an overridden method that the parent guaranteed to do something.

The fix is almost always in the design. Make the hierarchy reflect behaviour: introduce a Shape interface with area(), and let Rect and Sq be sibling implementations, each with an immutable design; or split Bird into Bird and FlyingBird. Or use composition rather than inheritance when the types are not truly interchangeable.

Interviewers use LSP to test whether you understand that is-a in code means substitutable behaviour. A good closing sentence: LSP is what makes polymorphism safe, because polymorphism assumes any subclass can stand in for the parent.

Code examples

Square breaks the Rectangle contract

class Rectangle {
    protected int w, h;
    void setWidth(int w)  { this.w = w; }
    void setHeight(int h) { this.h = h; }
    int area() { return w * h; }
}

class Square extends Rectangle {
    @Override void setWidth(int w)  { this.w = w; this.h = w; }
    @Override void setHeight(int h) { this.w = h; this.h = h; }
}

public class LspViolation {
    static void resize(Rectangle r) {
        r.setWidth(5);
        r.setHeight(4);
        System.out.println(r.area());   // client expects 20
    }

    public static void main(String[] args) {
        resize(new Rectangle());
        resize(new Square());
    }
}

The Square is not substitutable: the client code breaks when it receives one.

LSP-friendly redesign with immutable siblings (Java 16+)

interface Shape { int area(); }
record Rect(int w, int h) implements Shape { public int area() { return w * h; } }
record Sq(int side) implements Shape { public int area() { return side * side; } }

public class LspFixed {
    public static void main(String[] args) {
        System.out.println(new Rect(5, 4).area());
        System.out.println(new Sq(4).area());
    }
}

Key points

Concepts covered

SOLID, LSP, inheritance, contracts