Fixing LSP violations — composition vs inheritance
Fast recall
Quick revision
Core answer
Interview-ready guide
Interview answer
To fix an LSP violation, first identify the observable base guarantee that fails. The subtype may reject an input the base accepts, weaken its result, break an invariant, or remove a promised operation.
The fix can be to correct the subtype, narrow the base contract, split capabilities into interfaces, or remove the inheritance relationship. For the mutable rectangle and square case, separate immutable Shape implementations can expose area() without promising independent setters. A caching service can wrap a correct service through composition while preserving its contract.
Removing inheritance is common but not automatic. The final design is correct when every advertised implementation passes the same contract tests and clients no longer need subtype-specific checks or exceptions.
Fixing LSP violations — composition vs inheritance
Direct Answer
Fix an LSP violation by first identifying the broken base guarantee. You may correct the subtype, narrow or redesign the base contract, split capabilities into interfaces, or replace inheritance with composition. Removing inheritance is common, but it is not the only fix; the result must make every advertised subtype honor the contract.
Code Example
// Common interface with no shared mutable state
interface Shape {
int getArea();
int getPerimeter();
}
// Rectangle and Square are independent implementations
record Rectangle(int width, int height) implements Shape {
@Override
public int getArea() { return width * height; }
@Override
public int getPerimeter() { return 2 * (width + height); }
}
record Square(int side) implements Shape {
@Override
public int getArea() { return side * side; }
@Override
public int getPerimeter() { return 4 * side; }
}
// Now clients depend on Shape interface
void processShape(Shape shape) {
var area = shape.getArea(); // Works for both, no surprises
System.out.println("Area: " + area);
}
Common Mistakes & Pitfalls
- Throwing UnsupportedOperationException: Freshers think overriding a method to throw this exception is a valid fix — it's actually another LSP violation because callers still break at runtime.
- Abstract parent class with no-op methods: Creating an abstract base class where some methods do nothing, then making subclasses override them — this just moves the LSP violation around.
- Keeping inheritance and documenting restrictions: Adding JavaDoc that says "don't call setHeight on Square" is not a fix — code shouldn't rely on documentation to avoid crashes.
- Over-engineering with interfaces: Creating an interface with 20 methods when inheritance would work fine — LSP violations only matter when the subclass actually breaks substitution.
Check your understanding
Answer these without looking back at the article.
- When is inheritance appropriate?
- What does favor composition over inheritance mean?
Looking for a structured roadmap?
Every module on this page is also part of the full Java backend interview track.