Liskov Substitution Principle
If someone hands you "a phone charger," you expect it to charge your phone — no matter which brand made it. If one particular charger secretly only "charges" phones that are already at 100%, it's lying about being a phone charger. Named after computer scientist Barbara Liskov, the Liskov Substitution Principle (LSP) says: a subclass must be usable anywhere its parent class is expected, without breaking the caller's expectations.
💡 The Bottom Line
If code works with a base class, it should keep working — unmodified — when you hand it any subclass instead. If using a subclass means the caller has to add special-case checks just to avoid it blowing up, that subclass has broken the contract of its parent.
The classic example: Rectangle and Square
Mathematically, a square is a rectangle with equal sides — so it seems reasonable to
make Square extends Rectangle. Here's where it goes wrong:
class Rectangle {
setWidth(w) { this.width = w; }
setHeight(h) { this.height = h; }
area() { return this.width * this.height; }
}
class Square extends Rectangle {
setWidth(w) { this.width = w; this.height = w; } // must keep both sides equal
setHeight(h) { this.width = h; this.height = h; }
}
function resizeAndCheck(rect) {
rect.setWidth(5);
rect.setHeight(10);
console.assert(rect.area() === 50, "Expected area 50");
}
resizeAndCheck(new Rectangle()); // passes: area is 50
resizeAndCheck(new Square()); // fails: area is 100 — setHeight silently changed width too
Any code written and tested against Rectangle silently breaks the moment a
Square is passed in instead. Square is a valid shape mathematically, but it's
not a valid substitute for Rectangle in code — it violates the caller's
assumption that width and height can be set independently.
⚠️ Watch Out
LSP violations are sneaky because the code still compiles and the inheritance still makes conceptual sense in plain English ("a square is a rectangle"). The break only shows up at runtime, often in a caller far away from where the subclass was defined — which is exactly why it's a "principle" to watch for, not something a type checker alone will catch.
Signs you're violating LSP
- A subclass overrides a method to throw an error or do nothing for cases the parent supported.
- Calling code has to check
if (obj instanceof SpecificSubclass)before calling a method, to avoid the subclass misbehaving. - A subclass strengthens preconditions (demands more than the parent did) or weakens postconditions (promises less than the parent did).
🙋 There Are No Dumb Questions
Q: So should Square never inherit from Rectangle?
A: Not through mutable setWidth/setHeight methods, no — that's exactly the
shape that breaks. If both were immutable (a Rectangle that only exposes a read-only
area(), built once at construction), a Square could satisfy that same contract
without surprises. The lesson isn't "never model Square as a Rectangle" — it's "design the
parent's contract so every subclass can honestly keep it."
The fix: design the contract subclasses can actually keep
Instead of forcing a hierarchy where it doesn't fit, model what's actually shared:
class Shape {
area() { throw new Error("must implement area()"); }
}
class Rectangle extends Shape {
constructor(width, height) { super(); this.width = width; this.height = height; }
area() { return this.width * this.height; }
}
class Square extends Shape {
constructor(side) { super(); this.side = side; }
area() { return this.side * this.side; }
}
Both are honest Shapes with an area() method that behaves exactly as
promised — neither one secretly mutates state the other didn't ask for.
🔧 Try It Yourself
A Bird base class has a fly() method. A Penguin subclass overrides
fly() to throw an error, since penguins can't fly. Explain why this violates LSP, and
sketch an alternative class design (hint: what if fly() weren't on the base
Bird class at all?).
✅ Quick Check
A function works correctly with every subclass of PaymentMethod — except one
subclass throws an unexpected error for an operation the parent class always supported. What
principle is being violated?
Next up: what happens when an interface forces classes to implement methods they don't actually need? That's the Interface Segregation Principle.