Search Knowledge

© 2026 LIBREUNI PROJECT

Liskov Substitution Principle (LSP)

Liskov Substitution Principle (LSP)

The Liskov Substitution Principle (LSP) is the “L” in SOLID. It was introduced by Barbara Liskov in 1987. It states:

“Objects in a program should be replaceable with instances of their subtypes without altering the correctness of that program.”

In simpler terms: A derived class should complement its base class, not contradict it.

The Classic Violation: Square and Rectangle

In geometry, a square is a rectangle. However, in software, this can lead to logic errors if not handled carefully.

The Violation (Python)

class Rectangle:
    def __init__(self, width, height):
        self._width = width
        self._height = height

    def set_width(self, w): self._width = w
    def set_height(self, h): self._height = h
    def get_area(self): return self._width * self._height

class Square(Rectangle):
    def set_width(self, w):
        self._width = w
        self._height = w # Constraints of a square

    def set_height(self, h):
        self._width = h
        self._height = h

Now, imagine a function that expects a Rectangle:

def increase_width(rect: Rectangle):
    rect.set_width(10)
    rect.set_height(5)
    # For a classic Rectangle, area should be 50.
    # For a Square, area will be 25 because set_height(5) overrode the width!
    assert rect.get_area() == 50 # This fails if rect is a Square!

The Square class is not a valid substitute for Rectangle in this context.

LSP Rules

  1. Contravariance of method arguments: A subclass should not require more than the superclass.
  2. Covariance of return types: A subclass should not return less than the superclass.
  3. No new exceptions: A subclass should not throw new exceptions that the client doesn’t expect.
  4. Preconditions cannot be strengthened: You can’t require more input data.
  5. Postconditions cannot be weakened: You must still guarantee the output.

Correct Design: Use Interfaces

Instead of inheritance, use a shared interface or rethink the hierarchy.

class Shape:
    def get_area(self):
        pass

class Rectangle(Shape):
    def __init__(self, w, h):
        self.w, self.h = w, h
    def get_area(self): return self.w * self.h

class Square(Shape):
    def __init__(self, side):
        self.side = side
    def get_area(self): return self.side ** 2

LSP in Java: Empty Implementations

A common LSP violation is when a subclass implements a method but leaves it empty or throws a “NotImplementedException”.

public interface Bird {
    void fly();
}

public class Duck implements Bird {
    public void fly() { System.out.println("Flying..."); }
}

public class Ostrich implements Bird {
    public void fly() {
        throw new UnsupportedOperationException("Ostriches can't fly!");
    }
}

If you pass an Ostrich to a function expecting Bird, and that function calls .fly(), the program crashes. Ostrich broke the contract of Bird.

How to Fix LSP Violations

  • Refactoring to Interface: Split the interface (e.g., FlyingBird vs SwimmingBird).
  • Composition: Instead of inheriting, have the class contain an instance of the other.
  • Is-A vs Behaves-Like: Ensure the subclass truly behaves like the parent.

Benefits

  • Predictability: You can trust that any object of type T will behave according to T’s contract.
  • Robustness: Reduces runtime errors and “special case” handling in calling code.