Introduction

Inheritance is powerful, but it is also easy to misuse.
Sometimes classes are connected with “is‑a” relationships that sound right in English but cause bugs in code.

The Liskov Substitution Principle (LSP) helps you decide when inheritance makes sense and how to design base and derived classes so they can be used safely in place of one another.
This article explains LSP in beginner‑friendly language, shows classic examples of breaking it, and then guides you toward designs that keep your hierarchies valid and predictable.

What Is the Liskov Substitution Principle?

The Liskov Substitution Principle says:

If S is a subtype of T, then objects of type T in a program may be replaced with objects of type S without altering the correctness of that program.

In simpler words:

  • Wherever your code expects a base class object, it should work just as well if you give it any of its subclasses.

  • Subclasses must honor the promises (contract) of the base class; they should not surprise callers.

So if Bird has a method fly(), and some bird subclasses cannot fly (like Penguin), making Penguin inherit from Bird with fly() might break LSP, because code that expects “all Birds can fly” will fail when given a Penguin.

LSP is about behavior, not just type compatibility.

Base Class as a Contract

Think of a base class as a contract:

  • Its public methods, parameters, return values, and documented behavior form an agreement with any code that uses it.

  • Any subclass must respect that agreement.

For example, imagine a simple PaymentMethod base class:

The contract here is:

  • pay attempts to pay amount.

  • It returns true for success, false for failure.

  • It should not, for example, randomly throw exceptions for normal inputs that other payment types handle.

Any subclass (CardPayment, WalletPayment, UPIPayment) is substitutable if:

  • It implements pay in a way that still makes sense to callers who only know about PaymentMethod.

  • It does not add surprising restrictions that break existing code’s assumptions.

Classic LSP Violation: Rectangle and Square

The rectangle‑square example is often used to illustrate LSP.

First Idea: Square Inherits from Rectangle

You might start with:

Now, you think:

 “A square is a special case of a rectangle, so Square should inherit from Rectangle.”

Seems logical in math, but consider this function that expects a Rectangle&:

If you use it with Rectangle:

But with Square:

Code that expects a generic rectangle (width and height independent) behaves incorrectly when given a Square.

Square breaks the contract of Rectangle (“width and height can be set independently”).

This means Square is not a proper subtype of Rectangle in this design, and LSP is violated.

What This Example Teaches

The key lesson is:

  • Just because something is “a kind of” something else in the real world or in math does not mean it should inherit from it in code.

  • If the subclass must change or break the base class’s expected behavior, it is not LSP‑compliant.

To fix this, you might:

  • Avoid modeling Square as a subclass of Rectangle.

  • Instead, have both Rectangle and Square implement a common Shape interface with area().

  • Or design the base class so that setters are not part of the contract (for example, using immutable shapes).

The general idea is to make sure that substitution will not surprise the code that uses the base type.

LSP in Service‑Style Classes

LSP is not only about geometric shapes.
It also applies to service classes and domain types in LLD.

Example: PaymentMethod from earlier. Suppose you write code like this:

If you create a subclass like this:

While technically ,it inherits from PaymentMethodIt changes the behavior in a way that callers might not expect if the base contract assumed “no exceptions for valid amounts.”

Code that was written only against PaymentMethod might be broken when given StrangePayment.

To respect LSP:

  • Document and honor the behavior of the base class in subclasses.

  • Do not add surprising new failure modes or constraints that the base class did not have.

  • If you need stricter rules, you might need a different abstraction instead of forcing it into the same hierarchy.

Designing Hierarchies That Follow LSP

Here are practical guidelines to keep LSP in mind when designing:

  1. Define a clear contract in the base class

    • Describe what methods do, what inputs they accept, and what outputs or side effects are expected.

    • Use comments or documentation to make the expectations explicit.

  2. Subclasses should not weaken the contract

    • They should not require “more” (e.g., only accept special cases of inputs where the base accepts more).

    • They should not provide “less” (e.g., skip important guarantees like invariants or postconditions).

  3. Avoid inheritance when behavior changes fundamentally.

    • If a subclass would need to throw in places where the base would succeed, or ignore some methods entirely, that’s a red flag.

    • In those cases, prefer:

      • A different interface or base class, or

      • Composition instead of inheritance (have a member object instead of being a subtype).

  4. Test with base‑typed references

    • Write tests that use base class references or pointers and plug in each subclass.

    • If any subclass behaves surprisingly compared to others, check if the base contract or the subclass design needs to change.

LSP and Other SOLID Principles

LSP works closely with other SOLID principles:

  • With the Single Responsibility Principle (SRP)

    • If your base class has a clear, focused responsibility, it’s easier to define a clean contract that subclasses can follow.

  • With Open/Closed Principle (OCP)

    • You often extend behavior by adding new subclasses.

    • LSP ensures that these new subclasses do not break existing code that uses the base type.

  • With Dependency Inversion

    • High‑level modules depend on abstractions (interfaces).

    • LSP ensures that any implementation of those interfaces can be substituted safely.

If your subclasses routinely violate LSP, your use of OCP and dependency inversion becomes fragile, because new implementations might silently break existing flows.

Summary

The Liskov Substitution Principle states that subclasses should be usable anywhere their base class is expected without breaking correctness.
In other words, if code works with a base type, it should also work with any of its subtypes, without needing special checks or branches.

In this article, you learned:

  • The formal idea of LSP and a plain‑language explanation.

  • Why do base classes act as contracts that subclasses must respect?

  • The classic rectangle‑square example and how it illustrates LSP violations.

  • How LSP applies to service classes like payment methods and other LLD components.

  • Practical guidelines for designing hierarchies and avoiding inheritance when it would break substitutability.

Used together with SRP and OCP, LSP helps you design inheritance hierarchies that are not only flexible but also safe and predictable in real applications.