Skip to content
FollowtheDamnCode

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.

Roles, not one fat interface

In code

Each client asks for the one role it needs
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 1

Run 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

ProsCons
Clients are isolated from changes they don't care aboutMore interface types to name
No stubbed-out, lying methodsComposing roles needs care (a function wanting two roles)
Test doubles are tinyToo fine-grained is as confusing as too fat

How it relates

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.