Lesson 4 of 5

Interface Segregation Principle

Imagine a job application that makes every applicant — barista, accountant, or truck driver — fill out a section on "forklift certifications" and "public speaking experience," even though the job has nothing to do with either. Most applicants leave those sections blank or write "N/A." That's wasted effort forced on people by a form that doesn't fit their actual job. The Interface Segregation Principle (ISP) says the same about code: no class should be forced to depend on methods it doesn't use.

💡 The Bottom Line

Prefer several small, focused interfaces over one giant "do everything" interface. A class implementing an interface should need every single method on it — if it's stubbing out methods with "not supported" or empty bodies, the interface is too fat and needs splitting.

The trap: one bloated interface

class Worker {
  work()  { /* ... */ }
  eat()   { /* ... */ }
  sleep() { /* ... */ }
}

// A robot is a Worker too, right?
class RobotWorker extends Worker {
  work()  { /* does the work */ }
  eat()   { throw new Error("Robots don't eat"); }
  sleep() { throw new Error("Robots don't sleep"); }
}

RobotWorker is forced to "implement" eat() and sleep() just to satisfy the shape of Worker — and it does so by throwing errors, which is exactly the kind of landmine that violates the Liskov Substitution Principle from the last lesson too. ISP and LSP often go wrong together: a fat interface tempts subclasses into breaking substitutability.

⚠️ Watch Out

A method body that throws "not supported", returns null for everything, or is just empty "to satisfy the interface" is the clearest smell of an ISP violation. It means the interface bundled together capabilities that don't actually always travel as a set.

Fixing it: split by capability

class Workable { work()  { /* ... */ } }
class Eatable  { eat()   { /* ... */ } }
class Sleepable{ sleep() { /* ... */ } }

// HumanWorker needs all three
class HumanWorker {
  work()  { /* ... */ }
  eat()   { /* ... */ }
  sleep() { /* ... */ }
}

// RobotWorker only implements what actually applies to it
class RobotWorker {
  work() { /* does the work */ }
}

Now RobotWorker only signs up for what it can genuinely do. Code that needs "anything that works" can depend on just the work-related shape, without dragging in eating and sleeping it will never call.

🙋 There Are No Dumb Questions

Q: Isn't this basically the same idea as Single Responsibility?
A: They're closely related but aimed at different things. SRP is about a class having one reason to change. ISP is about an interface (the contract callers depend on) not forcing unrelated capabilities on every implementer. A class can follow SRP perfectly and still be forced to implement a bloated interface it only needs 20% of.

Why this matters for the caller's side too

ISP isn't only about implementers — it also protects the code that uses an interface. A function that only needs to call .print() should depend on a narrow "Printable" shape, not on a giant "Machine" interface with forty other methods it will never call. That way, unrelated changes to those other thirty-nine methods can never force a recompile, retest, or re-review of code that only ever cared about printing.

🔧 Try It Yourself

Picture an interface called MultiFunctionPrinter with print(), scan(), and fax(). A cheap printer model only supports printing. Sketch how you'd split MultiFunctionPrinter into smaller interfaces so the cheap printer's class never has to implement scan() or fax() at all.

✅ Quick Check

A class implements an interface with eight methods but only genuinely needs three of them — the rest are empty stubs. What does ISP recommend?


Next up: the last piece — how should high-level code depend on low-level code without being glued to one specific implementation? That's the Dependency Inversion Principle.

← Back to
Next →