Break The Hierarchy for Adhering to LSP
What LSP actually requires — and why renaming a method doesn't satisfy it
The Liskov Substitution Principle: objects of a subtype must be usable anywhere the base type is expected,
without breaking the program's correctness. A subclass may not weaken a guarantee the base type makes. The
earlier page "fixed" an LSP violation by renaming ElectricCar.refuel() to recharge() and
declared LSP satisfied — but renaming changes nothing: any code holding a Car and calling
refuel() still breaks for an electric car. LSP is about behavioural substitutability, not method
names.
The canonical violation: Square is-a Rectangle
class Rectangle {
protected int w, h;
void setWidth(int w) { this.w = w; }
void setHeight(int h) { this.h = h; }
int area() { return w * h; }
}
class Square extends Rectangle { // "a square IS a rectangle" — mathematically yes, behaviourally NO
void setWidth(int w) { this.w = w; this.h = w; } // forced to keep sides equal
void setHeight(int h) { this.w = h; this.h = h; }
}
// client code written against Rectangle:
void resizeAndCheck(Rectangle r) {
r.setWidth(5);
r.setHeight(4);
assert r.area() == 20; // holds for Rectangle; FAILS for Square (area == 16)
}
Passing a Square to resizeAndCheck breaks an assertion that holds for every
Rectangle — so Square is not a behavioural subtype. The inheritance is the bug.
The fix: restructure so subtypes keep every base promise
Don't force an "is-a" that violates behaviour. Give them a common abstraction that only promises what both can honour, and drop the false inheritance:
interface Shape { int area(); } // the honest common contract
class Rectangle implements Shape {
private final int w, h;
Rectangle(int w, int h) { this.w = w; this.h = h; }
public int area() { return w * h; }
}
class Square implements Shape {
private final int side;
Square(int side) { this.side = side; }
public int area() { return side * side; }
}
Same lesson for the cars: a base Car should only promise what all cars do (e.g.
drive()). Refuelling is not universal, so it doesn't belong on Car — put it on a
GasolineCar / Refuelable abstraction. Then no caller of Car can invoke something a
subtype can't honour.
How to spot an LSP violation before it ships
- A subclass overrides a method to throw ("not supported") — it's refusing a base promise.
- A subclass strengthens a precondition (needs more than the base) or weakens a postcondition (delivers less).
- Client code has
if (x instanceof Subtype)special-cases — a sign the subtype isn't truly substitutable.
When inheritance is still right — and when composition wins
- Choose inheritance only if every base method is honourable for all inputs the base advertises. If any subtype must refuse, throw, or silently change the postcondition, the inheritance is a lie.
- Thin common base is enough when both types share a small honest contract (e.g.
Shape.area()). Do not put mutators on that base just because one subtype has them. - Full capability interfaces (ISP-style) when roles diverge:
Flyable,Refuelable— not one fatBird/Carthat every subtype pretends to implement. - Prefer composition when the type needs a storage engine or partial reuse without claiming to be that engine (Stack has-a List; ElectricCar has-a Battery). See Using Composition to Follow LSP for the Stack/ArrayList worked fix.
- Beyond cars and shapes: collections that leak mutators, framework base classes that force no-op overrides, and domain "is-a" that is only marketing (Admin is-a User with extra methods that break User contracts) all need the same test: can every base call succeed for the subtype?
Follow-up drills
- Penguin / fly.
Birdhasfly();Penguincannot fly. Is the hierarchy wrong or the method wrong? Defend both repairs: (a) removeflyfromBird, introduceFlyable; (b) keepBirdwithout flight and compose flight capability. Which fits a zoo domain where most birds fly? instanceofas smell. Client code doesif (shape instanceof Square) { ... } else if (shape instanceof Rectangle) { ... }. What does that prove about the abstraction? Refactor so the client only callsarea()(or a visitor if you truly need type-specific ops on a closed set).- Numeric recap. Walk
setWidth(5); setHeight(4);on Square-as-Rectangle and state the area that fails the assert: 16, not 20.
Takeaways
- LSP is behavioural: a subtype must honour every guarantee of its base, not merely share method names.
- Renaming/adjusting a method doesn't fix a violation; restructuring the hierarchy does.
- If a subtype can't fulfill a base method, it shouldn't inherit it — split the abstraction or compose.
- Inheritance is a contract bet; lose the bet and pay with composition or a thinner base.
Re-authored for correctness for this guide (the prior version claimed renaming refuel→recharge satisfied LSP). Per Barbara Liskov's substitution principle & Martin's SOLID. See also: Composition for LSP, Interface Segregation.
🤖 Don't fully get this? Learn it with Claude
Stuck on Break The Hierarchy for Adhering to LSP? Open Claude, copy a block below, and it'll teach you this exact concept — visually and interactively.
Build the mental picture, not memorization.
I just read a lesson on **Break The Hierarchy for Adhering to LSP** (OO & Low-Level Design) and want to truly understand it. Explain Break The Hierarchy for Adhering to LSP from first principles using ONE vivid real-world analogy and a visual mental model — draw it as ASCII art or a clear step-by-step diagram — with a concrete example using real numbers. Then ask me one question to check I got the mental picture, and wait for my reply. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
Socratic — adapts to where you're stuck.
Teach me **Break The Hierarchy for Adhering to LSP** interactively. Ask me ONE guiding question at a time, wait for my answer, and adapt to my confusion — build the idea with me step by step instead of explaining it all at once. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
Active recall exposes what you missed.
Quiz me on **Break The Hierarchy for Adhering to LSP** with 5 questions, easy to tricky, ONE at a time. Tell me if each answer is right; at the end, explain clearly what I got wrong and why. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
Intuition + hook + flashcards for long-term memory.
Help me remember **Break The Hierarchy for Adhering to LSP** for the long term: give the one-sentence intuition, a memorable hook/mnemonic, a tiny worked example, and 3 active-recall flashcards (Q -> A). If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.