Skip to content

Liskov Substitution Principle ​

Subtypes must be usable wherever their base type is expected.

Intent ​

If a function accepts a Rectangle, passing a Square (when Square is-a Rectangle) must not break the function’s assumptions.

Problem ​

It feels natural: a square is a rectangle. So Square subclasses Rectangle and overrides setters so sides stay equal.

But callers of Rectangle assume setting height only changes height. Square also rewrites width. The contract breaks.

Violation (this example) ​

use_it saves width, sets height = 10, expects area == width * 10.

Structure ​

PieceRole
RectangleIndependent width / height
SquareSetters keep sides equal (breaks parent contract)
use_itClient that trusts Rectangle behavior

Compare code ​

Subtype changes the meaning of setters

python
class Square(Rectangle):
    @Rectangle.width.setter
    def width(self, value):
        self._width = self._height = value

    @Rectangle.height.setter
    def height(self, value):
        self._height = self._width = value

Client assumes Rectangle rules

python
def use_it(rc: Rectangle):
    w = rc.width
    rc.height = 10
    expected = int(w * 10)
    print(f"Expected: {expected}, Actual: {rc.area}")

Walkthrough ​

Quick check ​

Why does Square break LSP here?

Run locally ​

bash
uv run lsp

Expected shape:

text
Expected: 20, Actual: 20
Expected: 50, Actual: 100

Source: main.py · tutorial README

Takeaway ​

  • Inheritance is a behavioral promise, not just reuse
  • If the subtype can’t honor the parent contract, don’t inherit that way
  • Prefer a shared Shape (or similar) over forcing Square into Rectangle

Better direction ​

Don’t model a mutable square as a rectangle subtype — keep them as siblings under a common abstraction, or drop independent setters on a shared base.