Liskov Substitution Principle
IntermediateSubtypes must be substitutable for their base types without altering the correctness of the program.
Overview
LSP (Barbara Liskov, 1987) formalizes correct inheritance. A subclass is LSP-compliant if it can replace its parent everywhere without changing behavior. In terms of contracts: a subclass may only weaken preconditions (accept more inputs), strengthen postconditions (return more specific outputs), and must preserve all invariants of the parent. Classic violation: Square extends Rectangle — setWidth() on a Square also changes height (to keep it square), breaking the Rectangle contract that width and height are independent. LSP violations manifest as downcasts (instanceof checks), type-specific behavior, and unexpected exceptions in inherited methods.
LSP Violation: Square-Rectangle Problem
Square extends Rectangle but violates LSP because setting width on a Square also sets height — breaking the Rectangle invariant that the two dimensions are independent.
// ❌ Classic LSP violation
public class Rectangle {
protected int width;
protected int height;
public void setWidth(int width) { this.width = width; }
public void setHeight(int height) { this.height = height; }
public int area() { return width * height; }
}
public class Square extends Rectangle {
@Override
public void setWidth(int width) {
this.width = width;
this.height = width; // side effect — violates Rectangle contract!
}
@Override
public void setHeight(int height) {
this.width = height; // side effect
this.height = height;
}
}
// Client code that breaks with Square
public void testRectangle(Rectangle r) {
r.setWidth(5);
r.setHeight(4);
assert r.area() == 20 : "Expected 20, got " + r.area();
// Passes for Rectangle — fails for Square (area = 16 not 20)!
}
testRectangle(new Rectangle()); // ✅ passes
testRectangle(new Square()); // ❌ assertion fails — LSP violated
// ✅ Fix: do not use inheritance; use separate classes with a common interface
public interface Shape {
int area();
}
public final class Rectangle implements Shape {
private final int width, height;
public Rectangle(int width, int height) { this.width = width; this.height = height; }
@Override public int area() { return width * height; }
}
public final class Square implements Shape {
private final int side;
public Square(int side) { this.side = side; }
@Override public int area() { return side * side; }
}LSP in Practice: Checked Exceptions and Contracts
Subclasses must not throw new checked exceptions not declared by the parent, must not strengthen preconditions, and must not weaken postconditions.
// Parent contract: readFile throws IOException
public class FileReader {
public String readFile(String path) throws IOException {
return Files.readString(Path.of(path));
}
}
// ❌ LSP violation: subclass throws unchecked exception for valid inputs
public class StrictFileReader extends FileReader {
@Override
public String readFile(String path) throws IOException {
if (!path.endsWith(".txt")) {
throw new IllegalArgumentException("Only .txt files allowed"); // new precondition!
}
return super.readFile(path);
}
}
// Client using FileReader — breaks if receives a StrictFileReader with a .json path
FileReader reader = new StrictFileReader();
reader.readFile("data.json"); // unexpected IllegalArgumentException
// ✅ LSP-compliant: subclass accepts same inputs, may return more specific output
public class CachingFileReader extends FileReader {
private final Map<String, String> cache = new HashMap<>();
@Override
public String readFile(String path) throws IOException {
// Same precondition, stronger postcondition (may return cached value)
return cache.computeIfAbsent(path, p -> {
try { return super.readFile(p); }
catch (IOException e) { throw new UncheckedIOException(e); }
});
}
}
// Rule summary:
// Precondition: subclass must accept AT LEAST what parent accepts (weaken or keep same)
// Postcondition: subclass must return AT LEAST what parent promises (strengthen or keep same)
// Invariants: subclass must maintain all parent invariantsKey Points to Remember
- 1Subtypes must be substitutable for their base type without breaking program correctness.
- 2Square-Rectangle is the canonical LSP violation — Square breaks Rectangle's independent-dimensions invariant.
- 3Subclasses may only weaken preconditions (accept more) and strengthen postconditions (return more).
- 4Throwing new unchecked exceptions for valid parent inputs violates LSP.
- 5instanceof checks in client code are a red flag — they indicate the caller knows about subtypes, signaling an LSP problem.
Interview Questions
Sign in to ask AriaExplain the Square-Rectangle LSP violation and how to fix it.
What does "weaken preconditions, strengthen postconditions" mean in LSP?
How do instanceof checks in client code signal an LSP violation?
Is it ever acceptable to violate LSP?
Ask Aria about Liskov Substitution Principle
Your personal AI tutor — ask anything about this concept
Revision Status
Personal Notes
Sign in to save personal notes for this topic.
Discussion
Sign in to join the discussion.