Interface Segregation
TL;DR: No class should be forced to implement methods it doesn't use. If your
Workerinterface haswork(),eat(), andsleep(), you're forcing aRobotto fake eating and sleeping. Split the interface so each class only commits to what it can actually do.

Why This Matters in Interviews
ISP violations surface constantly in LLD interviews because candidates tend to build one big interface that covers every possible behavior. When the interviewer says "now add a robot worker," the cracks show immediately: the Robot has to implement eat() and sleep() with empty bodies or throw exceptions. That's the same kind of contract violation you saw with LSP, but the root cause is different. LSP says "don't break promises." ISP says "don't make promises you can't keep in the first place."
Interviewers look for whether you can design interfaces that are specific enough that every implementor genuinely supports every method.
The Problem: One Fat Interface
// BAD: Worker forces every implementor to support all three methods
interface Worker {
void work();
void eat();
void sleep();
}
class HumanWorker implements Worker {
@Override
public void work() {
System.out.println("Writing code");
}
@Override
public void eat() {
System.out.println("Eating lunch");
}
@Override
public void sleep() {
System.out.println("Sleeping 8 hours");
}
}
class Robot implements Worker {
@Override
public void work() {
System.out.println("Assembling parts");
}
@Override
public void eat() {
// Robots don't eat... but we're forced to implement this
}
@Override
public void sleep() {
// Robots don't sleep... but we're forced to implement this
}
}
// BAD: Same problem in C++
class Worker {
public:
virtual ~Worker() = default;
virtual void work() = 0;
virtual void eat() = 0;
virtual void sleep() = 0;
};
class HumanWorker : public Worker {
public:
void work() override { std::cout << "Writing code" << std::endl; }
void eat() override { std::cout << "Eating lunch" << std::endl; }
void sleep() override { std::cout << "Sleeping 8 hours" << std::endl; }
};
class Robot : public Worker {
public:
void work() override { std::cout << "Assembling parts" << std::endl; }
void eat() override { /* Robots don't eat */ }
void sleep() override { /* Robots don't sleep */ }
};
# BAD: Same problem in Python
from abc import ABC, abstractmethod
class Worker(ABC):
@abstractmethod
def work(self) -> None:
pass
@abstractmethod
def eat(self) -> None:
pass
@abstractmethod
def sleep(self) -> None:
pass
class HumanWorker(Worker):
def work(self) -> None:
print("Writing code")
def eat(self) -> None:
print("Eating lunch")
def sleep(self) -> None:
print("Sleeping 8 hours")
class Robot(Worker):
def work(self) -> None:
print("Assembling parts")
def eat(self) -> None:
pass # Robots don't eat
def sleep(self) -> None:
pass # Robots don't sleep
What's Wrong Here?
The Robot class is lying. It claims to support eat() and sleep() because it implements the Worker interface, but those methods do nothing. Any code that calls eat() on a Worker is silently broken when the worker is a Robot. This leads to two problems:
- Silent failures — calling
robot.eat()doesn't crash, but it also doesn't do what the caller expects. This is worse than crashing because it hides bugs. - Coupling to irrelevant behavior — if you add a
takeVitamins()method toWorker, you have to updateRobottoo, even though vitamins have nothing to do with robots.
Interview tip: Empty method implementations are a design smell. When you see a class implementing a method with an empty body or a comment like "not applicable," the interface is too broad. That's your cue to split it.
The Fix: Split Into Focused Interfaces
// GOOD: Each interface represents one capability
interface Workable {
void work();
}
interface Feedable {
void eat();
}
interface Restable {
void sleep();
}
class HumanWorker implements Workable, Feedable, Restable {
@Override
public void work() {
System.out.println("Writing code");
}
@Override
public void eat() {
System.out.println("Eating lunch");
}
@Override
public void sleep() {
System.out.println("Sleeping 8 hours");
}
}
class Robot implements Workable {
@Override
public void work() {
System.out.println("Assembling parts");
}
// No eat(). No sleep(). No empty stubs. No lies.
}
// GOOD: Same fix in C++
class Workable {
public:
virtual ~Workable() = default;
virtual void work() = 0;
};
class Feedable {
public:
virtual ~Feedable() = default;
virtual void eat() = 0;
};
class Restable {
public:
virtual ~Restable() = default;
virtual void sleep() = 0;
};
class HumanWorker : public Workable, public Feedable, public Restable {
public:
void work() override { std::cout << "Writing code" << std::endl; }
void eat() override { std::cout << "Eating lunch" << std::endl; }
void sleep() override { std::cout << "Sleeping 8 hours" << std::endl; }
};
class Robot : public Workable {
public:
void work() override { std::cout << "Assembling parts" << std::endl; }
};
# GOOD: Same fix in Python
from abc import ABC, abstractmethod
class Workable(ABC):
@abstractmethod
def work(self) -> None:
pass
class Feedable(ABC):
@abstractmethod
def eat(self) -> None:
pass
class Restable(ABC):
@abstractmethod
def sleep(self) -> None:
pass
class HumanWorker(Workable, Feedable, Restable):
def work(self) -> None:
print("Writing code")
def eat(self) -> None:
print("Eating lunch")
def sleep(self) -> None:
print("Sleeping 8 hours")
class Robot(Workable):
def work(self) -> None:
print("Assembling parts")
Now Robot only implements Workable. It makes no promises about eating or sleeping. Code that needs a Feedable will never accidentally receive a Robot — the type system prevents it.
// Type safety: this method can only receive things that actually eat
void scheduleLunch(List<Feedable> workers) {
for (Feedable worker : workers) {
worker.eat(); // Guaranteed to work — Robot can't appear here
}
}
How ISP and LSP Work Together
ISP and LSP attack the same family of problems from different angles:
| Principle | What it prevents | How |
|---|---|---|
| LSP | Subclass breaks the base class contract | Fix the hierarchy after the violation |
| ISP | Class is forced to implement irrelevant methods | Prevent the violation by splitting the interface upfront |
If you apply ISP correctly, you avoid most LSP violations before they happen. The Robot never gets a fly() or eat() method it can't support because it never implements an interface that promises those.
Interview tip: When designing interfaces, ask: "Would every implementor of this interface genuinely support every method?" If the answer is no, the interface needs splitting. It's much cheaper to split an interface during design than to fix LSP violations after you've built a class hierarchy on top of it.
Quick Recap
| Concept | What it means | Why it matters |
|---|---|---|
| No forced implementations | Classes should only implement methods they actually use | No empty stubs, no silent no-ops |
| Focused interfaces | Each interface represents one cohesive capability | Implementors commit only to what they can deliver |
| Prevents LSP violations | If a class never promises behavior it can't fulfill, substitutability is preserved | Catches design mistakes at the interface level, not the class level |
| Composition of interfaces | Classes implement multiple small interfaces as needed | HumanWorker is Workable + Feedable + Restable; Robot is just Workable |