Liskov Substitution Principle (LSP)
The Liskov Substitution Principle is one of the SOLID principles of object-oriented programming. It was introduced by Barbara Liskov in 1987 and it states:
“Objects of a superclass should be replaceable with objects of its subclasses without affecting the correctness of the program.”
What It Really Means:
Imagine you’re working with a class called Bird that has a method fly(). If you create a subclass Penguin from Bird, but Penguin can’t fly, then substituting Bird with Penguin could break your program. That’s a violation of LSP.
The principle pushes us to design subclasses that truly behave like their parent class, so code that uses the base class doesn’t have to worry about the specific subclass being used.
LSP in Action (Simple Example):
class Bird {
public:
virtual void fly() {
std::cout << "Flying..." << std::endl;
}
};
class Sparrow : public Bird {
public:
void fly() override {
std::cout << "Sparrow flying!" << std::endl;
}
};
// This class violates LSP
class Ostrich : public Bird {
public:
void fly() override {
throw std::logic_error("Ostriches can't fly!");
}
};
In the example above, Ostrich breaks LSP because it can’t perform the fly() behavior expected from Bird.
Why It Matters:
- Encourages safe inheritance.
- Makes code more reliable and maintainable.
- Helps avoid unexpected runtime errors.
- Ensures that your class hierarchy makes logical sense.
How to Stick to LSP:
- Don’t override methods in a way that changes their expected behavior.
- Use composition over inheritance if subclass behavior doesn’t match the base class.
- Clearly define contracts (what methods are supposed to do) and ensure subclasses respect them.
What is LSP?
Definition by Barbara Liskov:
If S is a subtype of T, then objects of type T may be replaced with objects of type S without altering the correctness of the program.
Simple Version:
A subclass should be able to stand in for its parent class without breaking the program.
What does that mean?
Let’s break it down:
- You create a base class (Parent class).
- Then you make a derived class (Child class).
- The child should behave in a way that’s expected from the parent.
- You should be able to use the child wherever you used the parent, and things should just work.
Real-Life Analogy
Let’s say:
- You have a class “Bird” with a method
fly(). - You create a subclass “Parrot” — fine, it can fly.
- Now you create a subclass “Penguin” — 🐧 uh-oh! Penguins can’t fly.
If you call fly() on a Penguin object assuming it’s a Bird, your program breaks the expectation.
🚫 That violates LSP!
Example in C++ that Violates LSP
class Bird {
public:
virtual void fly() {
cout << "Flying..." << endl;
}
};
class Sparrow : public Bird {
public:
void fly() override {
cout << "Sparrow flying" << endl;
}
};
class Ostrich : public Bird {
public:
void fly() override {
throw runtime_error("Ostriches can't fly!");
}
};
Problem:
- If someone uses
Bird* b = new Ostrich(); b->fly();expecting it to fly — it crashes. Ostrichis a Bird, but it doesn’t behave like a Bird should.
This violates LSP.
LSP-Compliant Design
Solution: Refactor your classes to respect behavior expectations.
class Bird {
public:
virtual void eat() {
cout << "Bird eating" << endl;
}
};
class FlyingBird : public Bird {
public:
virtual void fly() = 0;
};
class Sparrow : public FlyingBird {
public:
void fly() override {
cout << "Sparrow flying" << endl;
}
};
class Ostrich : public Bird {
// No fly() here
};
Now:
Ostrichdoesn’t pretend to be a flying bird.- If your logic requires only flying birds, you use the
FlyingBirdtype. - LSP is safely respected.
LSP in Code: A Simple, Clear Example
class Rectangle {
public:
virtual void setWidth(int w) { width = w; }
virtual void setHeight(int h) { height = h; }
virtual int getArea() { return width * height; }
protected:
int width;
int height;
};
Now we make a Square from Rectangle:
class Square : public Rectangle {
public:
void setWidth(int w) override {
width = height = w;
}
void setHeight(int h) override {
width = height = h;
}
};
Problem:
If someone does this:
Rectangle* r = new Square();
r->setWidth(5);
r->setHeight(10);
cout << r->getArea(); // Expected: 50, But it gives 100
Uh-oh! Because setting width or height sets both in Square.
This breaks expected behavior.
It violates LSP.
LSP-Compliant Refactor
Instead, don’t inherit Square from Rectangle directly if they behave differently.
Maybe use composition or separate hierarchy.
LSP Summary Table
| Concept | Bad (Violates LSP) | Good (Follows LSP) |
|---|---|---|
Penguin inherits Bird with fly() | Penguins can’t fly | Separate class for non-flying birds |
| Square inherits Rectangle | Area breaks expectations | Design Square separately |
| Subclass breaks parent’s rule | Throws, skips behavior | Behaves as parent promises |
Key Tips to Follow LSP
- Derived classes must honor contracts/behavior of base class.
- Don’t override methods in a way that changes expected behavior.
- Use interfaces or abstract classes to separate capabilities (like flying).
- Prefer composition over inheritance when behaviors differ.
Want to Try a LSP-based Mini Project?
We can build:
- A
PaymentMethodbase class (pay()) - Subclasses:
CreditCard,UPI,Wallet - And see how breaking or respecting LSP affects real logic
FAQ on Liskov Substitution Principle (LSP) in C++
1. What is the Liskov Substitution Principle (LSP) in C++?
The Liskov Substitution Principle (LSP) is one of the SOLID principles of object-oriented programming. It states that objects of a derived class should be replaceable with objects of their base class without affecting the correctness of the program. In simple terms, subclasses should behave like their parent classes without breaking functionality.
2. Why is the Liskov Substitution Principle important in software design?
LSP ensures code reliability, reusability, and maintainability. By following it, developers prevent unexpected behaviors when using inheritance. It reduces bugs, improves abstraction, and makes the system easier to extend in the future.
3. Can you give a simple C++ example of the Liskov Substitution Principle?
Yes.
#include
using namespace std;
class Bird {
public:
virtual void fly() { cout << "Bird can fly\n"; }
};
class Sparrow : public Bird {
public:
void fly() override { cout << "Sparrow flying high\n"; }
};
// Substitution works correctly
int main() {
Bird* b = new Sparrow();
b->fly(); // Works fine as Sparrow behaves like Bird
delete b;
return 0;
}
Here, Sparrow can substitute Bird without breaking the program, which follows LSP.
4. What happens if LSP is violated in C++?
Violating LSP leads to runtime errors, unexpected behaviors, and poor code design. For example, if a derived class overrides a base class function in a way that changes its expected behavior, the system may produce wrong results or even crash.
5. How does the Liskov Substitution Principle relate to C++ design patterns?
Many C++ design patterns, such as Strategy, Template Method, and Factory Method, rely on LSP to ensure that subclasses can replace base classes seamlessly. Without LSP, these patterns lose their flexibility and correctness.
6. How can I check if my C++ code follows the Liskov Substitution Principle?
You can verify by asking:
- Can I replace every instance of the base class with the subclass?
- Does the subclass maintain the expected behavior of the parent?
If the answer is yes, then your code follows LSP.
7. What are real-life examples of the Liskov Substitution Principle?
- Vehicles: A
Caris a type ofVehicleand can be used wherever aVehicleis expected. - Shapes: A
Circleshould behave like aShapewithout altering the expected behavior of aShapeclass. - Payment Systems: A
CreditCardPaymentshould work wherever a genericPaymentMethodis required.
8. How does LSP improve C++ code quality?
- Promotes polymorphism
- Reduces tight coupling
- Makes code testable and scalable
- Encourages clean architecture with fewer bugs
9. What is the difference between LSP and the Open-Closed Principle (OCP)?
- LSP ensures that subclasses can replace base classes without breaking functionality.
- OCP ensures that classes are open for extension but closed for modification.
Both are connected — violating LSP often means violating OCP as well.
10. How is Liskov Substitution Principle used in modern C++ projects in 2025?
In 2025 C++ development, LSP is applied in:
- Embedded systems for safe hardware abstraction
- Game development for character and object hierarchies
- Enterprise applications for scalable and maintainable architectures
- Design patterns to ensure flexible and reusable solutions
You can also Visit other tutorials of Embedded Prep
- What is eMMC (Embedded MultiMediaCard) memory ?
- Top 30+ I2C Interview Questions
- Bit Manipulation Interview Questions
- Structure and Union in c
- Little Endian vs. Big Endian: A Complete Guide
- Merge sort algorithm
Special thanks to @mr-raj for contributing to this article on Embedded Prep
Mr. Raj Kumar is a highly experienced Technical Content Engineer with 7 years of dedicated expertise in the intricate field of embedded systems. At Embedded Prep, Raj is at the forefront of creating and curating high-quality technical content designed to educate and empower aspiring and seasoned professionals in the embedded domain.
Throughout his career, Raj has honed a unique skill set that bridges the gap between deep technical understanding and effective communication. His work encompasses a wide range of educational materials, including in-depth tutorials, practical guides, course modules, and insightful articles focused on embedded hardware and software solutions. He possesses a strong grasp of embedded architectures, microcontrollers, real-time operating systems (RTOS), firmware development, and various communication protocols relevant to the embedded industry.
Raj is adept at collaborating closely with subject matter experts, engineers, and instructional designers to ensure the accuracy, completeness, and pedagogical effectiveness of the content. His meticulous attention to detail and commitment to clarity are instrumental in transforming complex embedded concepts into easily digestible and engaging learning experiences. At Embedded Prep, he plays a crucial role in building a robust knowledge base that helps learners master the complexities of embedded technologies.












