# Liskov Substitution Principle (LSP)

> "If S is a subtype of T, then objects of type T may be replaced with objects of type S without altering any of the desirable properties of the program." — Barbara Liskov

## Problem

A subclass overrides a parent's behavior in a way that breaks the expectations of code written against the parent type. The classic example: `Square` extends `Rectangle`. A rectangle allows width and height to vary independently, but a square enforces `width == height`. Code that sets width and height separately on a "Rectangle" breaks when handed a Square.

**Symptoms of LSP violation:**
- A subclass throws exceptions for methods the parent supports
- A subclass silently ignores or re-interprets parent method calls
- Callers need `isinstance` / type-checks to handle subtypes differently
- Tests that pass for the parent fail for the child

## Solution

Ensure subtypes honor the **contract** (preconditions, postconditions, invariants) of the parent type. If a subtype cannot fulfill the parent's contract, the inheritance relationship is wrong — use composition or a different hierarchy instead.

## When to Use

- You are designing an inheritance hierarchy and want to guarantee substitutability
- You notice callers doing type-checks to work around subclass quirks
- You want to be confident that polymorphic code works for all subtypes
- You are writing library code where users will extend your base classes

## When to Avoid

- You are not using inheritance at all (composition-only designs sidestep LSP)
- The "inheritance" is purely for code reuse with no polymorphic substitution
- The violation is trivial and contained within a small, private module

## Pseudocode

### Before — Square violates Rectangle's contract

```
CLASS Rectangle:
    width, height

    METHOD set_width(w):
        width = w

    METHOD set_height(h):
        height = h

    METHOD area():
        RETURN width * height

CLASS Square EXTENDS Rectangle:
    METHOD set_width(w):
        width = w
        height = w    // Breaks caller expectation!

    METHOD set_height(h):
        width = h     // Breaks caller expectation!
        height = h

// Caller code:
FUNCTION verify_area(rect: Rectangle):
    rect.set_width(5)
    rect.set_height(4)
    ASSERT rect.area() == 20   // FAILS for Square (gets 16)
```

### After — separate types, shared interface

```
INTERFACE Shape:
    METHOD area() -> number

CLASS Rectangle IMPLEMENTS Shape:
    width, height
    METHOD area(): RETURN width * height

CLASS Square IMPLEMENTS Shape:
    side
    METHOD area(): RETURN side * side

// No broken substitution — each type has its own honest contract.
```

## Python

```python
"""
Liskov Substitution Principle — Python

BEFORE: Square extends Rectangle and breaks the width/height contract.
AFTER:  Both are independent implementations of a Shape protocol.
"""

from abc import ABC, abstractmethod


# ============================================================
# BEFORE: Violating LSP
# ============================================================

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

    @property
    def width(self) -> float:
        return self._width

    @width.setter
    def width(self, value: float) -> None:
        self._width = value

    @property
    def height(self) -> float:
        return self._height

    @height.setter
    def height(self, value: float) -> None:
        self._height = value

    def area(self) -> float:
        return self._width * self._height


class SquareBefore(RectangleBefore):
    """Violates LSP: setting width also changes height and vice versa."""

    def __init__(self, side: float):
        super().__init__(side, side)

    @RectangleBefore.width.setter
    def width(self, value: float) -> None:
        self._width = value
        self._height = value  # Surprise!

    @RectangleBefore.height.setter
    def height(self, value: float) -> None:
        self._width = value   # Surprise!
        self._height = value


def verify_area_before(rect: RectangleBefore) -> None:
    """This function assumes independent width/height — breaks for Square."""
    rect.width = 5
    rect.height = 4
    expected = 20
    actual = rect.area()
    status = "PASS" if actual == expected else "FAIL"
    print(f"  {type(rect).__name__:>15}: width=5, height=4 => "
          f"area={actual:.0f} (expected {expected}) [{status}]")


# ============================================================
# AFTER: Following LSP — shared interface, independent contracts
# ============================================================

class Shape(ABC):
    @abstractmethod
    def area(self) -> float:
        ...

    @abstractmethod
    def describe(self) -> str:
        ...


class Rectangle(Shape):
    def __init__(self, width: float, height: float):
        self.width = width
        self.height = height

    def area(self) -> float:
        return self.width * self.height

    def describe(self) -> str:
        return f"Rectangle({self.width}x{self.height})"


class Square(Shape):
    def __init__(self, side: float):
        self.side = side

    def area(self) -> float:
        return self.side * self.side

    def describe(self) -> str:
        return f"Square(side={self.side})"


def print_shape_areas(shapes: list[Shape]) -> None:
    """Works with any Shape — no broken assumptions."""
    for shape in shapes:
        print(f"  {shape.describe():>25}  area = {shape.area():.2f}")


# ============================================================
# Demo
# ============================================================

def main():
    print("--- BEFORE (LSP violated) ---")
    verify_area_before(RectangleBefore(10, 10))  # PASS (happens to work)
    verify_area_before(SquareBefore(10))          # FAIL (contract broken)

    print("\n--- AFTER (LSP respected) ---")
    shapes: list[Shape] = [
        Rectangle(5, 4),
        Square(5),
        Rectangle(3, 7),
        Square(6),
    ]
    print_shape_areas(shapes)

    total = sum(s.area() for s in shapes)
    print(f"  {'TOTAL':>25}  area = {total:.2f}")


if __name__ == "__main__":
    main()
```

## Go

```go
// Liskov Substitution Principle — Go
//
// BEFORE: Square embeds Rectangle and overrides SetWidth/SetHeight, breaking the contract.
// AFTER:  Both implement a Shape interface with honest, independent contracts.

package main

import "fmt"

// ============================================================
// BEFORE: Violating LSP
// ============================================================

type RectangleBefore struct {
	Width  float64
	Height float64
}

func (r *RectangleBefore) SetWidth(w float64)  { r.Width = w }
func (r *RectangleBefore) SetHeight(h float64) { r.Height = h }
func (r *RectangleBefore) Area() float64        { return r.Width * r.Height }

type SquareBefore struct {
	RectangleBefore
}

func NewSquareBefore(side float64) *SquareBefore {
	return &SquareBefore{RectangleBefore{side, side}}
}

// Violates LSP: coupling width and height
func (s *SquareBefore) SetWidth(w float64)  { s.Width = w; s.Height = w }
func (s *SquareBefore) SetHeight(h float64) { s.Width = h; s.Height = h }

type Resizable interface {
	SetWidth(float64)
	SetHeight(float64)
	Area() float64
}

func verifyAreaBefore(label string, r Resizable) {
	r.SetWidth(5)
	r.SetHeight(4)
	expected := 20.0
	actual := r.Area()
	status := "PASS"
	if actual != expected {
		status = "FAIL"
	}
	fmt.Printf("  %15s: width=5, height=4 => area=%.0f (expected %.0f) [%s]\n",
		label, actual, expected, status)
}

// ============================================================
// AFTER: Following LSP
// ============================================================

// Shape defines an honest contract — just compute area.
type Shape interface {
	Area() float64
	Describe() string
}

type Rectangle struct {
	Width, Height float64
}

func (r Rectangle) Area() float64    { return r.Width * r.Height }
func (r Rectangle) Describe() string { return fmt.Sprintf("Rectangle(%.0fx%.0f)", r.Width, r.Height) }

type Square struct {
	Side float64
}

func (s Square) Area() float64    { return s.Side * s.Side }
func (s Square) Describe() string { return fmt.Sprintf("Square(side=%.0f)", s.Side) }

func PrintShapeAreas(shapes []Shape) {
	total := 0.0
	for _, s := range shapes {
		a := s.Area()
		total += a
		fmt.Printf("  %25s  area = %.2f\n", s.Describe(), a)
	}
	fmt.Printf("  %25s  area = %.2f\n", "TOTAL", total)
}

// ============================================================
// Demo
// ============================================================

func main() {
	fmt.Println("--- BEFORE (LSP violated) ---")
	rect := &RectangleBefore{Width: 10, Height: 10}
	sq := NewSquareBefore(10)
	verifyAreaBefore("Rectangle", rect)
	verifyAreaBefore("Square", sq)

	fmt.Println("\n--- AFTER (LSP respected) ---")
	shapes := []Shape{
		Rectangle{Width: 5, Height: 4},
		Square{Side: 5},
		Rectangle{Width: 3, Height: 7},
		Square{Side: 6},
	}
	PrintShapeAreas(shapes)
}
```

## JavaScript

```javascript
// Liskov Substitution Principle — JavaScript
//
// BEFORE: Square extends Rectangle and overrides setters, breaking the contract.
// AFTER:  Both are independent classes implementing the same shape interface.

// ============================================================
// BEFORE: Violating LSP
// ============================================================

class RectangleBefore {
  constructor(width, height) {
    this._width = width;
    this._height = height;
  }

  get width() { return this._width; }
  set width(value) { this._width = value; }

  get height() { return this._height; }
  set height(value) { this._height = value; }

  area() { return this._width * this._height; }
}

class SquareBefore extends RectangleBefore {
  constructor(side) {
    super(side, side);
  }

  // Violates LSP: couples width and height
  set width(value) {
    this._width = value;
    this._height = value;
  }
  get width() { return this._width; }

  set height(value) {
    this._width = value;
    this._height = value;
  }
  get height() { return this._height; }
}

function verifyAreaBefore(shape, label) {
  shape.width = 5;
  shape.height = 4;
  const expected = 20;
  const actual = shape.area();
  const status = actual === expected ? "PASS" : "FAIL";
  console.log(`  ${label.padStart(15)}: width=5, height=4 => area=${actual} (expected ${expected}) [${status}]`);
}

// ============================================================
// AFTER: Following LSP — independent types, shared interface
// ============================================================

class Rectangle {
  constructor(width, height) {
    this.width = width;
    this.height = height;
  }

  area() {
    return this.width * this.height;
  }

  describe() {
    return `Rectangle(${this.width}x${this.height})`;
  }
}

class Square {
  constructor(side) {
    this.side = side;
  }

  area() {
    return this.side * this.side;
  }

  describe() {
    return `Square(side=${this.side})`;
  }
}

/** Works with any object that has area() and describe() — no broken contracts. */
function printShapeAreas(shapes) {
  let total = 0;
  for (const shape of shapes) {
    const a = shape.area();
    total += a;
    console.log(`  ${shape.describe().padStart(25)}  area = ${a.toFixed(2)}`);
  }
  console.log(`  ${"TOTAL".padStart(25)}  area = ${total.toFixed(2)}`);
}

// ============================================================
// Demo
// ============================================================

function main() {
  console.log("--- BEFORE (LSP violated) ---");
  verifyAreaBefore(new RectangleBefore(10, 10), "Rectangle");
  verifyAreaBefore(new SquareBefore(10), "Square");

  console.log("\n--- AFTER (LSP respected) ---");
  const shapes = [
    new Rectangle(5, 4),
    new Square(5),
    new Rectangle(3, 7),
    new Square(6),
  ];
  printShapeAreas(shapes);
}

main();
```

## Related Patterns

- **Factory Method** — factory method decouples callers from concrete types, making it easy to substitute subtypes.
- **Strategy** — strategy replaces fragile inheritance hierarchies with composition; strategies are trivially substitutable.
- **ISP** — a class that violates LSP often also violates ISP (it implements more than it can honour).
