Liskov Substitution Principle (LSP)
Any subclass must honor its parent's behavioral contract — swapping one for the other should never break the caller.
Intent & 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.
Real-world Use Case
📌 TL;DR
Subtypes must honor their parent’s contract. If substituting a subclass breaks a caller — fix the hierarchy, not the caller.
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
Disadvantages
- 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
// 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;
}
}