Interface Segregation Principle
Many small, role-shaped interfaces beat one fat one — no class should be forced to depend on methods it doesn't use.
Big idea
The Interface Segregation Principle (the I in SOLID): clients shouldn't be forced to depend on methods they don't use. Shape interfaces around what each caller needs, not around everything one class happens to do.
The problem it solves
An office Machine interface has print(), scan(), fax(), and staple(). The cheap desk printer must implement all four, so it ships three methods that raise NotImplementedError — a Liskov trap waiting for a caller. Worse, the payroll module only prints, yet it depends on Machine; when someone changes the fax() signature, payroll has to be recompiled, retested, and redeployed for a feature it never touches.
Think of it like
A universal TV remote with 60 buttons, of which you use five. Each extra button is something to misread, something that breaks, something you must learn. A remote with just power, volume, and channel is easier to use — and a guest who only wants the volume doesn't need the full manual.
How it works
Split the fat interface into roles: Printer, Scanner, Fax. A big machine implements several; a small one implements one. Each client asks for only the role it uses.
In code
from typing import Protocol
class Printer(Protocol):
def print_doc(self, doc: str) -> str: ...
class Scanner(Protocol):
def scan(self) -> str: ...
class DeskPrinter: # implements one role, honestly
def print_doc(self, doc):
return f"desk printed '{doc}'"
class OfficeHub: # implements two
def print_doc(self, doc):
return f"hub printed '{doc}'"
def scan(self):
return "hub scanned page 1"
def run_payroll(printer: Printer): # depends only on printing
return printer.print_doc("payslips")
def archive(scanner: Scanner): # depends only on scanning
return scanner.scan()
print(run_payroll(DeskPrinter()))
print(run_payroll(OfficeHub()))
print(archive(OfficeHub()))output
desk printed 'payslips'
hub printed 'payslips'
hub scanned page 1Run and edit it in the app.
No class carries a method it can't honour, and run_payroll will never be disturbed by a change to scanning.
Which is the clearest sign an interface should be split?
Watch it
When to apply it — and when not to
Split when implementers stub methods out, when unrelated clients share one interface, or when changes to one part of an interface ripple to clients that don't use it. Don't shatter every interface into one-method pieces up front: if every client uses all of it, it's already segregated.
Pros and cons
| Pros | Cons |
|---|---|
| Clients are isolated from changes they don't care about | More interface types to name |
| No stubbed-out, lying methods | Composing roles needs care (a function wanting two roles) |
| Test doubles are tiny | Too fine-grained is as confusing as too fat |
How it relates
- It prevents Liskov violations caused by impossible-to-honour methods.
- It's Single Responsibility applied to interfaces.
- Adapter and Facade often present a narrow interface over a wide one.
ISP says interfaces should be designed around…
Key terms
- Interface Segregation Principle — No client should depend on methods it doesn't use.
- Role interface — A small interface describing one thing a client needs, such as Printer.
- Fat interface — An interface bundling many unrelated operations, forcing implementers to stub some.