OOD Fundamentals
OOP Foundations
Creational Patterns
Structural Patterns
Behavioral Patterns
Classic OOD Problems: Part 1
Classic OOD Problems: Part 2
Liskov Substitution Principle
Every SOLID principle protects your codebase from a specific category of damage. The Liskov Substitution Principle (LSP) guards against the most insidious kind: code that compiles, passes type checks, and looks correct, but breaks at runtime because a subtype does not actually behave like its parent.
Barbara Liskov formulated the principle in 1987: If S is a subtype of T, then objects of type T can be replaced with objects of type S without altering the correctness of the program. The key word is "correctness" -- not "compiles" and not "runs without crashing." It means the program produces the same observable behavior. Every postcondition still holds. Every invariant remains intact. Every caller's assumptions stay valid.
Why does this matter? Because polymorphism is the backbone of object-oriented design. You write a function that accepts a Shape and calls area(). You pass it a Circle, a Triangle, a Polygon. The function does not know or care which concrete type it received. This only works if every subtype honors the contract of Shape. The moment one subtype silently changes what area() means -- returning perimeter instead, throwing an exception, or producing a negative number -- every function that depends on Shape becomes unreliable.

The Cost of Violating LSP
LSP violations do not announce themselves. They hide behind green test suites because developers test each subtype in isolation rather than through the parent type's interface. The bug surfaces when someone writes generic code -- a function that processes a list of Shape objects, a handler that accepts any PaymentMethod, a pipeline that works with any DataSource. That generic code was written against the parent's contract. When a subtype violates the contract, the generic code fails in ways the author never anticipated.
The debugging cost is high because the failure is far from the cause. The function that crashes is correct. The subtype that violates the contract is in a different file, written by a different developer, possibly months earlier. The connection between them is invisible unless you understand LSP.
LSP is not about inheritance mechanics -- it is about behavioral compatibility. A subtype can add new methods, new fields, and new capabilities. What it must not do is change the meaning of existing methods. The parent type makes promises to its callers. Every subtype must keep those promises.
LSP vs Type Checking
Modern type systems catch many errors at compile time. If Shape declares area() -> float, the compiler ensures every subtype returns a float. But LSP goes beyond types. The compiler cannot verify that area() returns a positive number, that withdraw() never puts a bank account below zero, or that sort() actually produces a sorted list. These are behavioral contracts -- promises about what methods do, not just what types they return. LSP demands that subtypes honor these behavioral contracts, not just the type signatures.