Interface Segregation Principle

Topics Covered

What Is Interface Segregation

Why This Matters Beyond Aesthetics

The Relationship to Other SOLID Principles

Fat Interfaces and Their Problems

How Fat Interfaces Form

The Four Costs of Fat Interfaces

Recognizing Fat Interfaces in Your Codebase

The Real-World Scale of This Problem

Designing Focused Interfaces

Segregated Interfaces for the Printer Problem

Role Interfaces: Design from the Client's Perspective

Composing Interfaces for Convenience

The Interface Segregation Decision Framework

Segregated Worker Interfaces

ISP in Practice

Refactoring a Fat Interface: Step by Step

ISP in Real Frameworks

Common Mistakes When Applying ISP

Practice: Apply ISP Yourself

Every SOLID principle exists because real codebases accumulate a specific kind of damage over time. The Interface Segregation Principle (ISP) targets one of the most common forms: interfaces that grow too large, forcing classes to implement methods they will never use.

The principle states: No client should be forced to depend on methods it does not use.

That sounds abstract, so let us make it concrete. Imagine you are building a document processing system. You start with a single interface that represents every operation a device might perform:

python
1from abc import ABC, abstractmethod
2
3class MultiFunctionDevice(ABC):
4    @abstractmethod
5    def print_document(self, doc: str) -> None:
6        pass
7
8    @abstractmethod
9    def scan_document(self) -> str:
10        pass
11
12    @abstractmethod
13    def fax_document(self, doc: str, number: str) -> None:
14        pass

An all-in-one office machine implements this interface cleanly because it genuinely supports printing, scanning, and faxing. But what happens when you need a SimplePrinter class? It can only print. Yet the interface forces it to implement scan_document and fax_document:

python
1class SimplePrinter(MultiFunctionDevice):
2    def print_document(self, doc: str) -> None:
3        print(f"Printing: {doc}")
4
5    def scan_document(self) -> str:
6        raise NotImplementedError("SimplePrinter cannot scan")
7
8    def fax_document(self, doc: str, number: str) -> None:
9        raise NotImplementedError("SimplePrinter cannot fax")

Two out of three methods throw exceptions. The SimplePrinter is lying about its capabilities. Any code that receives a MultiFunctionDevice has no way to know which methods actually work without either reading documentation or catching exceptions at runtime. This is the core problem ISP solves.

Fat interface forcing empty implementations

Why This Matters Beyond Aesthetics

The damage from violating ISP is not just ugly code. It creates three real engineering problems:

Broken substitutability. The Liskov Substitution Principle says you should be able to use any implementation of an interface interchangeably. When SimplePrinter throws NotImplementedError on scan_document, it violates that promise. Code that works with the all-in-one machine breaks silently with the simple printer.

Forced recompilation and redeployment. In compiled languages, adding a new method to the fat interface forces every implementing class to be recompiled and redeployed, even if they do not use the new method. In a microservice architecture with shared interface packages, this means redeploying services that have no logical reason to change.

Coupling to irrelevant change. When the fax protocol changes and fax_document gains a new parameter, SimplePrinter must be updated even though it has nothing to do with faxing. The class is coupled to a concern it does not participate in.

Interview Tip

ISP is not about making interfaces small for the sake of minimalism. It is about ensuring that every method in an interface is relevant to every class that implements it. A 10-method interface where all implementers genuinely use all 10 methods is perfectly fine. A 3-method interface where some implementers throw NotImplementedError on one method already violates ISP.

The Relationship to Other SOLID Principles

ISP works in concert with the other four principles. The Single Responsibility Principle says a class should have one reason to change. ISP says the same thing about interfaces: an interface should not force changes on implementers for reasons unrelated to their actual responsibility. The Dependency Inversion Principle says high-level code should depend on abstractions. ISP ensures those abstractions are the right size, so that depending on them does not drag in irrelevant concerns.

Think of ISP as the principle that keeps your abstractions honest. Without it, interfaces accumulate methods the way classes accumulate responsibilities, until they become so broad that implementing them requires compromises.