The Liskov Substitution Principle: Subtypes Must Be Substitutable

2026-04-28

The Liskov Substitution Principle (LSP) states: if S is a subtype of T, then objects of type T can be replaced with objects of type S without breaking the program. It sounds academic, but violations cause some of the most confusing bugs you'll encounter.

The classic violation: You model a Square as a subclass of Rectangle. A rectangle lets you set width and height independently. A square overrides the setters so that setting width also sets height (and vice versa). Now any code that relies on setting width without affecting height silently breaks:

function doubleWidth(rect: Rectangle) {
  rect.setWidth(rect.getWidth() * 2);
  // expects area = original_width * 2 * original_height
  // but with Square, height changed too
}

The function's postcondition — that only width changes — is violated. The subtype weakened a guarantee the parent type made.

LSP boils down to three rules for subtypes:

Real-world example: You have a FileStorage class with a save(data) method that always succeeds or throws an IOException. A teammate creates ReadOnlyStorage extends FileStorage where save() throws an UnsupportedOperationException. Every caller that passes around a FileStorage reference now risks a surprise exception they never handled. This is a textbook LSP violation — the subtype strengthened the precondition (now requires the caller to know it's not read-only) and broke the postcondition (save no longer saves).

The fix: Use composition or split the interface. Create a ReadableStorage interface and a WritableStorage interface. ReadOnlyStorage implements only ReadableStorage. Code that needs to write demands a WritableStorage. No surprises.

A practical rule of thumb: Before creating a subclass, apply the "test every caller" check. Pick three places the parent type is used and mentally substitute your subtype. If any caller would need an instanceof check or would behave incorrectly, you're violating LSP. If more than zero callers break, don't inherit — compose or redesign the hierarchy.

How to spot violations in code review: Look for methods that throw UnsupportedOperationException, override methods with empty bodies, or add instanceof checks before calling a method. These are almost always LSP red flags. Another signal: subclass methods that ignore parameters the parent uses.

See it in action: Check out Liskov Substitution Principle by Him Khy Official to see this theory applied.
Key Takeaway: If your subclass would surprise any caller expecting the parent type, you don't have a valid subtype — split the interface or use composition instead of inheritance.

All newsletters