A Subclass Must Be a Replaceable Substitute for Its Base Class
The Liskov Substitution Principle (LSP) states that objects of a derived class should be able to replace objects of the base class without affecting the correctness of the program.
Simply put:
If class B inherits from class A, then anywhere we use A, we should be able to use B and the program should continue to work correctly.
This principle is not only about method signatures but, more importantly, about behavior. A derived class must not change the expected semantics of methods inherited from the base class.
Real-Life Example: How to Understand It?
Imagine a car rental company.
A customer walks in and says:
“I’d like to rent a car.”
The rental company hands over the keys and points to a vehicle in the parking lot.
The customer assumes that the car will:
- start when the key is turned,
- stop when the brake pedal is pressed,
- move when the accelerator is pressed.
Now imagine the rental company gives the customer a vehicle with the engine removed.
It still looks like a car. It has a key, a steering wheel, and pedals. However, when the key is turned, nothing happens.
Is it still a “car” according to the agreement with the customer?
Technically, yes.
Practically, absolutely not.
The customer has every right to expect that every vehicle provided by the rental company behaves like a car. The rental company violated that expectation by supplying an object that looks like a car but does not behave like one.
Programming works exactly the same way.
If we have a Car class with a start() method and create a subclass CarWithoutEngine that overrides the method and throws an exception, we violate LSP.
Any piece of code that expects a Car receives an object that does not fulfill the contract.
A Thought Experiment
Consider the classic programming example of a rectangle and a square.
At first glance, a square is a rectangle (every square is a rectangle).
But should a Square class inherit from a Rectangle class in code?
Let’s see where things break down.
Example — LSP Violation
Suppose we are building a system for calculating the areas of geometric shapes.
We start with a rectangle:
Mathematically, a square is a special type of rectangle, so we inherit from it:
Now let’s write a method that operates on a Rectangle and see what happens when we pass a Square.
Following:
What’s Wrong with This Approach?
The derived class changes the behavior of base-class methods.
In the Rectangle class, setWidth() changes only the width.
In the Square class, the same method also changes the height, without the caller’s knowledge.
Code that trusted the contract of the base class no longer works correctly.
The testArea() method does not know that it received a Square, and it should not need to know.
That is the essence of an LSP violation.
The inheritance hierarchy reflects a mathematical relationship, not a programming relationship.
In geometry, a square is a rectangle.
In object-oriented programming, that is not necessarily true if their behavior differs.
Example — LSP-Compliant Design
Instead of forcing a square into a rectangle hierarchy, we extract a shared abstraction representing what both shapes truly have in common: the ability to calculate an area.
Then:
// Rectangle - independent dimensions
class Rectangle extends Shape {
private int width;
private int height;
public Rectangle(int width, int height) {
this.width = width;
this.height = height;
}
@Override
public int calculateArea() {
return width * height;
}
}
Add a class type:
Now a method operating on Shape works correctly for both classes:
What Did We Gain?
Each class is responsible for its own logic.
The Square class no longer pretends to be a rectangle. It has its own consistent behavior.
The base-class contract is always honored.
calculateArea() in every subclass does exactly what the caller expects: it returns the area of the shape.
We can safely substitute subclasses.
Any code that accepts a Shape will work correctly whether it receives a Rectangle, a Square, or any other future shape.
Why Is LSP Important?
Inheritance Is a Promise
When we write:
class B extends A
we are making a promise:
“B behaves like A wherever A is expected.”
Violating LSP breaks that promise and introduces bugs that are often difficult to debug because the code still compiles successfully.
Predictable Behavior
When LSP is respected, we can trust that subclasses will not introduce surprises.
A method that works for the base class will also work for every subclass without requiring special handling.
Eliminating instanceof Checks
The need to write code such as:
if (object instanceof Square)
is often a sign of an LSP violation.
The code must distinguish between types because subclasses are not truly substitutable for their base class.
Better Team Collaboration
In large projects, LSP violations often surface months later.
Someone adds a new subclass and suddenly part of the system behaves differently.
Respecting LSP gives the team confidence that extending class hierarchies is safe.
Consequences in Large Systems
Imagine a payment system with a base class Payment and dozens of subclasses representing different payment methods.
If one subclass throws:
UnsupportedOperationException
for a method such as:
refund()
the refund mechanism breaks for that payment type.
Customers do not receive their money back, and the bug may only be discovered in production.
When Should You Apply LSP?
Whenever You Use Inheritance
Before creating a subclass, ask yourself:
Can this subclass replace the base class in every scenario?
If the answer is:
“Not always”
then the inheritance hierarchy is likely incorrect.
When instanceof Appears in the Code
Type checking is often a sign that subclasses are not true substitutes for the base class.
In E-Commerce Systems
Consider an Order class and an ExportOrder subclass.
If ExportOrder does not support calculateDiscount() in the same way as a normal order, it violates LSP and can cause failures in the discount system.
In Spring Framework
Spring heavily relies on LSP.
Any Bean can be replaced by a mock implementation during testing.
That is one of the reasons dependency injection through interfaces is so powerful: every implementation acts as a valid substitute.
“If it looks like a duck and quacks like a duck—but requires batteries to quack—you have a bad inheritance model.”
A subclass must not only look like the base class—it must also behave like it.
No surprises.
No exceptions.
No silent behavioral changes.
Inheritance is not just about sharing code—it is a commitment to honoring a contract.
A derived class may extend the behavior of a base class, but it should never break that behavior.

No responses yet