Liskov Substitution Principle
Objects of a subtype must be usable anywhere objects of their parent type are expected, without breaking the correctness of the program.
Also known as: LSP, Liskov Substitution, Behavioral Subtyping, Substitutability
Category: Software Development
Tags: software-development, software-design, object-oriented, design-patterns, architecture, maintainability, principles
Explanation
If your code works with a `Bird`, it should keep working when someone hands it a `Penguin`. If it doesn't (because `fly()` throws an exception), the type hierarchy is lying to you.
Barbara Liskov introduced the idea in a 1987 conference keynote titled "Data abstraction and hierarchy". In 1994, she and Jeannette Wing formalized it in "A Behavioral Notion of Subtyping": if a property is provable about objects of a type T, it should also hold for objects of a subtype S. It's the L in SOLID.
**The core idea**:
A subtype is a promise. It says "you can use me wherever you'd use my parent". The Liskov Substitution Principle (LSP) asks you to keep that promise at the level of behavior, beyond method signatures. The compiler checks that the methods exist. It can't check that they behave the way callers expect.
Robert C. Martin points out that this is about subtyping, not inheritance: every implementation of an interface is a subtype of that interface. His short version: "A program that uses an interface must not be confused by an implementation of that interface."
**The classic example: squares and rectangles**:
Mathematically, a square is a rectangle. So it seems natural to make `Square` extend `Rectangle`. But a `Rectangle` lets you set its width and height independently, and a `Square` can't: setting its width also changes its height. Now take code that sets a rectangle's width to 5 and its height to 4, and expects an area of 20. Pass it a `Square`, and it gets 16. The subtype broke the parent's behavior, even though every method exists and everything compiles.
**The rules of behavioral subtyping**:
The rules on preconditions and postconditions are identical to the ones Bertrand Meyer introduced for Design by Contract in 1988:
- **Preconditions can't be strengthened** in the subtype: it must accept at least everything the parent accepts
- **Postconditions can't be weakened**: it must guarantee at least everything the parent guarantees
- **Invariants must be preserved**: what's always true for the parent stays true for the subtype
- **The history rule**: the subtype can't allow state changes the parent would never allow
**Signs of an LSP violation**:
- Subclasses that throw `NotImplementedException` or `UnsupportedOperationException` for inherited methods
- Code that checks the concrete type (`instanceof`, type switches) before calling a method
- Overrides that silently do nothing
- Tests written for the parent type that fail when run against a subtype
**How to fix it**:
Often, the hierarchy itself is wrong. Split the parent type into smaller interfaces (e.g., `Bird` and `FlyingBird`, see the Interface Segregation Principle), or use composition instead of inheritance.
**In AI skills**:
LSP translates to swappable implementations. When several search skills exist with incompatible interfaces, they can't replace each other. The fix is to define the contract (what each skill expects as input and produces as output), so that any implementation respecting it can be swapped in.
Related Concepts
← Back to all concepts