Chaturmind
LearnDSASystem DesignBlogPremium
Sign inGet started
Chaturmind

Structured learning paths for engineers who want to go deep. Written by practitioners.

Learn

  • Java
  • DSA
  • System Design
  • Spring Boot
  • AI / ML

Company

  • Blog
  • Premium
  • Contact

Legal

  • Privacy Policy
  • Terms of Service

© 2026 Chaturmind. All rights reserved.

Built for engineers who want to go deep.


← Low-Level Design & Design Patterns

OOP Fundamentals & SOLID Principles

  • Object-Oriented Design Refresher
  • Single Responsibility & Open/Closed
  • Liskov Substitution & Interface Segregation
  • Dependency Inversion

Creational Design Patterns

  • Singleton Pattern
  • Factory & Abstract Factory
  • Builder Pattern
  • Prototype Pattern

Structural Design Patterns

  • Adapter & Facade
  • Decorator Pattern
  • Composite & Proxy
  • Bridge & Flyweight
Chaturmind
← Low-Level Design & Design Patterns

OOP Fundamentals & SOLID Principles

  • Object-Oriented Design Refresher
  • Single Responsibility & Open/Closed
  • Liskov Substitution & Interface Segregation
  • Dependency Inversion

Creational Design Patterns

  • Singleton Pattern
  • Factory & Abstract Factory
  • Builder Pattern
  • Prototype Pattern

Structural Design Patterns

  • Adapter & Facade
  • Decorator Pattern
  • Composite & Proxy
  • Bridge & Flyweight
HomeLearnSystem DesignLow-Level Design & Design PatternsOOP Fundamentals & SOLID Principles
✓ FreeIntermediate· 8 min read

Liskov Substitution & Interface Segregation

LSP through the classic Square-extends-Rectangle trap, and ISP through the Worker interface anti-pattern — the third and fourth SOLID letters, in code.

Published September 22, 2026


Liskov Substitution & Interface Segregation

LSP: subtypes must be substitutable for their base type

Barbara Liskov's formal statement: if S is a subtype of T, objects of type T should be replaceable with objects of type S without altering the correctness of the program. In practice: any code written against the base type must keep working, unmodified, when handed any subtype.

The classic violation: Square extends Rectangle

class Rectangle {
    protected int width, height;
    void setWidth(int w) { width = w; }
    void setHeight(int h) { height = h; }
    int area() { return width * height; }
}

class Square extends Rectangle {
    @Override void setWidth(int w) { width = w; height = w; } // forces both to stay equal
    @Override void setHeight(int h) { width = h; height = h; }
}

Mathematically, a square is a rectangle — so this looks reasonable. But run this code, written entirely against the Rectangle type:

void resize(Rectangle r) {
    r.setWidth(5);
    r.setHeight(10);
    assert r.area() == 50; // holds for Rectangle, FAILS for Square (area becomes 100)
}

Any Rectangle-typed code that assumes setting width and height independently produces width * height breaks the moment a Square is substituted in — exactly the failure LSP is defined to prevent. The bug isn't in the math (a square genuinely is a rectangle); it's that Square changes the behavioral contract Rectangle established (independent width/height mutation), which is what LSP actually protects — behavior, not just is-a relationships from a domain model.

The fix: don't model Square extends Rectangle at all — model both as implementations of a Shape interface exposing only area(), with no shared mutable setter contract to violate.

ISP: many small interfaces over one fat interface

The Worker anti-pattern

interface Worker {
    void work();
    void eat();
}

class HumanWorker implements Worker {
    public void work() { /* ... */ }
    public void eat() { /* ... */ }
}

class RobotWorker implements Worker {
    public void work() { /* ... */ }
    public void eat() { throw new UnsupportedOperationException(); } // forced to implement something meaningless
}

RobotWorker is forced to implement eat() even though it's meaningless for a robot — this is exactly the smell ISP names: a class is forced to depend on (and implement) methods it doesn't use. Beyond being awkward, it's dangerous — any caller holding a Worker reference can legally call .eat() on a RobotWorker and get a runtime exception instead of a compile-time guarantee that the operation makes sense.

The ISP fix: split by capability

interface Workable { void work(); }
interface Eatable { void eat(); }

class HumanWorker implements Workable, Eatable { ... }
class RobotWorker implements Workable { ... } // simply doesn't implement Eatable — no dead method

Each class implements exactly the capabilities it genuinely has. No UnsupportedOperationException landmines, and the type system itself now documents which capabilities each class actually supports.

LSP and ISP are related, not identical

ISP violations often cause LSP violations: a fat interface forces implementers to fake methods they don't really support (via no-ops or exceptions), and any caller programming against the interface can no longer trust that every implementation behaves the way the interface implies — which is precisely an LSP break. Splitting the interface (ISP) is frequently the fix that also restores LSP compliance.

Follow-up questions this topic invites — and their answers

Q: Is every is-a relationship automatically LSP-safe? A: No — Square/Rectangle is the canonical proof. Is-a from a domain-modeling standpoint ("a square is mathematically a kind of rectangle") and is-a from a behavioral substitutability standpoint (LSP's actual concern) can diverge; LSP cares about the latter.

Q: How would you detect an LSP violation in code review, concretely? A: Look for subclasses that override a method to throw an exception, return a different/narrower type than callers expect, strengthen preconditions, or weaken postconditions compared to the base type's documented contract — any of these break substitutability even if they compile cleanly.

Q: Doesn't ISP just mean 'lots of tiny interfaces everywhere'? A: The goal is interfaces segmented by cohesive capability, not interfaces minimized to one method each arbitrarily — over-splitting without a real capability boundary just adds interface-management overhead without the actual benefit ISP is after.

Q: Can a class legitimately need to implement a 'fat' interface with many methods? A: Yes, if every one of those methods is genuinely part of one cohesive capability that class actually has — ISP objects to forced, unused dependencies, not to interface size in isolation.

Previous

Single Responsibility & Open/Closed

Next

Dependency Inversion

AI Tutor

Lesson: Liskov Substitution & Interface Segregation

Quick actions

AI responses can be inaccurate. Verify critical information.