Liskov Substitution Principle
The Liskov Substitution Principle (LSP) requires subtypes to behave as expected wherever the base type appears.
Introduction
The Liskov Substitution Principle (LSP) requires subtypes to behave as expected wherever the base type appears. If Square extends Rectangle but setWidth breaks setHeight invariants, callers of Rectangle break — classic LSP failure.
Interviewers use LSP to judge inheritance choices. Subclasses must not strengthen preconditions, weaken postconditions, or throw unexpected exceptions.
When LSP is hard to satisfy, switch to composition or a shared interface without inheritance.
Understanding the topic
Key concepts
- Subtypes must honor base type contracts.
- No surprising exceptions or no-ops in overrides.
- Behavioral subtyping, not just signature matching.
- Preconditions cannot be strengthened in subclass.
- Postconditions cannot be weakened in subclass.
- Invariants of superclass preserved in subclass.
Step-by-step explanation
- Define explicit contract for base type methods.
- Write substitution test: would client code still work with subtype?
- Check overrides for stricter validation or different exceptions.
- Prefer interface segregation over forced inheritance.
- Use composition when behavior diverges significantly.
- Document allowed overrides in abstract base.
Informative example
Bird interface avoids Penguin extends FlyingBird LSP trap:
public interface Bird {String name();}public interface Flyer extends Bird {void fly();}public final class Sparrow implements Flyer {private final String name;public Sparrow(String name) { this.name = name; }@Override public String name() { return name; }@Override public void fly() { /* ... */ }}public final class Penguin implements Bird {private final String name;public Penguin(String name) { this.name = name; }@Override public String name() { return name; }}
Penguin is Bird but not Flyer — no fake fly() that throws UnsupportedOperationException.
Real-world use
Real-world applications
- Validating inheritance in shape, account, and stream hierarchies.
- API design for extensible frameworks.
- Interview traps involving square-rectangle or read-only collections.
Best practices
- Design interfaces around what all implementers truly share.
- Throw UnsupportedOperationException sparingly — signals LSP smell.
- Use factory to return most specific usable type.
- Test subclass with base type contract tests.
- Favor composition when behavior subsets differ.
Common mistakes
- Empty override or silent no-op changing meaning.
- Subclass throws where parent succeeded.
- Narrowing input types in override (invalid in Java anyway).
- Returning null where parent never did.
Advanced interview questions
Q1BeginnerWhat is LSP?
Q2BeginnerFamous LSP violation?
Q3IntermediateHow does LSP relate to inheritance?
Q4IntermediateIs throwing in override always LSP violation?
Q5AdvancedFix read-only list extending full List interface.
Summary
Subtypes must honor superclass behavioral contracts. Avoid inheritance when behavior diverges. Interface segregation prevents forced unsupported methods. Test substitution, not just compile-time types. Composition alternative when LSP fails.