The Liskov substitution principle (LSP) states that 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. It was formulated by Barbara Liskov in a 1987 keynote. It is the L in SOLID.
The principle is about behavioural subtyping, not just structural subtyping. A subclass is allowed to extend a base class syntactically (override methods, add fields), but it must honour the contract of the base class: preconditions cannot be strengthened, postconditions cannot be weakened, and invariants must be preserved.
The textbook example is a Square extending Rectangle:
class Rectangle: def set_width(self, w): self.width = w def set_height(self, h): self.height = h def area(self): return self.width * self.height class Square(Rectangle): def set_width(self, w): self.width = w self.height = w # square constraint: sides must be equal def set_height(self, h): self.width = h self.height = h
Code that works correctly with Rectangle will break with Square:
def scale_width(shape, factor): old_height = shape.height shape.set_width(shape.width * factor) assert shape.height == old_height # FAILS for Square
The Rectangle contract says changing the width does not change the height. Square violates this. Substituting a Square for a Rectangle breaks the function, violating LSP.
LSP implies that inheritance should model an “is-a” relationship in terms of behaviour, not just structure. A square is geometrically a special case of a rectangle, but it is not a behavioural subtype of a mutable rectangle. The fix is either to make Rectangle immutable, or not to use inheritance here at all.
The principle guides when to use inheritance versus composition: if you cannot substitute freely, the “is-a” relationship is wrong and composition is likely better.