# Liskov Substitution Principle (LSP)

> Any subclass must honor its parent's behavioral contract — swapping one for the other should never break the caller.

- **Category**: Solid
- **Subcategory**: principles
- **Canonical URL**: https://designpattern.fyi/patterns/liskov-substitution/

---

## Description
**Intent**: Subtypes must honor the full behavioral contract of their base type — not just the method signatures. A caller using the base type must never need to know or care which subtype is actually running.

**Context**: The Square-extends-Rectangle trap: Square forces width == height on every setWidth/setHeight call. Any caller that independently sets width and height on a Rectangle is silently broken when handed a Square. The code compiles; the behavior is wrong.

**Solution**: If Square and Rectangle break substitutability, they don't have an inheritance relationship — model them as separate subclasses of a shared Shape base. Subclasses should extend behavior without contradicting their parent's contract.



## Use Cases
Designing inheritance hierarchies where polymorphism matters. Ensuring that any subclass can stand in for its parent without callers noticing. Catching "is-a" misuse before it produces subtle production bugs.



## Implementation Example

```javascript
// Before: Violates LSP
class Rectangle {
  constructor(width, height) {
    this.width = width;
    this.height = height;
  }

  setWidth(width) {
    this.width = width;
  }

  setHeight(height) {
    this.height = height;
  }

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

class Square extends Rectangle {
  constructor(side) {
    super(side, side);
  }

  setWidth(width) {
    this.width = width;
    this.height = width; // Breaks rectangle behavior
  }

  setHeight(height) {
    this.width = height;
    this.height = height; // Breaks rectangle behavior
  }
}

// After: LSP applied
class Shape {
  getArea() {
    throw new Error('Must implement getArea');
  }
}

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

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

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

  getArea() {
    return this.side ** 2;
  }
}
```



## Trade-offs


### Advantages

- Reliable polymorphism — callers never need defensive type checks

- Inheritance hierarchies are predictable and safe to extend

- Surfaces "is-a" misuse early — sometimes composition is the real answer




### Considerations & Drawbacks

- Limits how dramatically subclasses can diverge from the parent's contract

- Requires careful upfront hierarchy design

- Forces honest modeling — not every code-reuse opportunity is a real "is-a" relationship







