What rules must an overriding method follow to respect the Liskov Substitution Principle?
It must accept at least what the parent accepts, promise at least what the parent promises, and throw no new kinds of failure. Weaker preconditions, stronger postconditions, same invariants.
Explain it simply
A bank branch promises: "Give me any valid ID and I will open your account." A sub-branch that adds "and a salary slip of Rs 5 lakh" has narrowed what it accepts, and customers sent by the head office are now rejected. The sub-branch cannot replace the original.
Three rules in practice: do not demand more from inputs, do not deliver less in results, and do not break promises the parent class makes about its state.
Example
class Storage:
def save(self, data: str) -> int:
# Promise: accepts any string, returns bytes written (>= 0)
...
class BadStorage(Storage):
def save(self, data: str) -> int:
if len(data) > 100: # narrower input: breaks callers
raise ValueError("too long")
return -1 # worse promise: negative size
class GoodStorage(Storage):
def save(self, data: str) -> int:
return len(data.encode()) # accepts all strings, returns >= 0Throwing UnsupportedOperationException from an overridden method is a common LSP smell for the same reason.
What to say in the interview
"An override should not strengthen preconditions, should not weaken postconditions, and should not throw new unexpected exceptions or break invariants. If callers need an instanceof check to use the subtype safely, LSP is broken."
Common mistakes
- Counting only compile-time signature matching as proof of substitutability.