Interface Segregation Principle (ISP)
Status: Complete
Category: SOLID Principles
Default enforcement: Advisory
Author: PushBackLog team
Tags
- Topic: quality, architecture
- Methodology: SOLID
- Skillset: any
- Technology: generic
- Stage: execution, review
Summary
No client should be forced to depend on interfaces it does not use. Large interfaces should be split into smaller, more specific ones so that implementing classes only need to know about the methods that are relevant to them.
Rationale
Fat interfaces spread coupling
When an interface has 15 methods and a class only needs 3 of them, that class still depends on all 15. Any change to any of those 15 methods forces recompilation (or re-evaluation) of every class that implements or imports the interface, even if the change is irrelevant to that class. Fat interfaces create unnecessary coupling between unrelated clients.
Implementing unused methods leads to LSP violations
Classes forced to implement methods they don’t need must do something with them — usually throw NotImplementedException or return a useless default. Either outcome violates the Liskov Substitution Principle: callers using the interface cannot trust that every method is safe to call.
Role interfaces vs header interfaces
Martin Fowler distinguishes a “header interface” (a mirror of all methods a given class happens to have) from a “role interface” (a narrow interface expressing a specific capability a client needs). ISP is an argument for role interfaces. Readable, Writable, Printable are better than a monolithic Document interface. The client depends on only the role it cares about.
Guidance
Signs an interface is too fat
- Implementations throw
NotImplementedExceptionfor some methods - Different clients use mutually exclusive subsets of the interface
- New methods added to the interface break implementations that had nothing to do with the new feature
- The interface name ends in
Manager,Handler, orServicewith no further qualification
How to segregate
- Identify which methods are actually used by each client
- Group methods by the role they express
- Extract a focused interface for each role
- Where a class still needs to play multiple roles, it can implement multiple interfaces
// Before: fat interface
interface UserRepository {
findById(id: string): User;
save(user: User): void;
delete(id: string): void;
findAll(): User[];
search(query: string): User[];
export(format: string): Buffer;
}
// After: segregated by role
interface UserReader { findById(id: string): User; findAll(): User[]; }
interface UserWriter { save(user: User): void; delete(id: string): void; }
interface UserSearcher { search(query: string): User[]; }
interface UserExporter { export(format: string): Buffer; }
A UserService that only reads can depend solely on UserReader. The caching layer, search service, and export service each depend only on the interface they need.
Examples
Worker interface violation
// Violates ISP — every implementor must deal with every method
interface Worker {
work(): void;
eat(): void;
sleep(): void;
}
class Robot implements Worker {
work() { /* ... */ }
eat() { throw new Error('Robots do not eat'); } // Forced fiction
sleep() { throw new Error('Robots do not sleep'); }
}
Corrected with role interfaces
interface Workable { work(): void; }
interface Feedable { eat(): void; }
interface Restable { sleep(): void; }
class Human implements Workable, Feedable, Restable { ... }
class Robot implements Workable { ... } // Only depends on what it can do
Practical test: can you write this implementation without stubs?
If you find yourself writing fake or stub implementations for interface methods just to satisfy the compiler, the interface is too broad. Each method on an interface should be something every implementor can meaningfully provide.
Anti-patterns
1. The god interface
One interface for everything a subsystem can do. Any change to any feature forces recompilation of all clients. Often named IService, IManager, or IHandler.
2. Stub implementations to satisfy fat interfaces
export() { throw new Error('Not supported'); }
If an implementation must throw for methods it “doesn’t use”, the interface is wrong, not the implementation.
3. Adding methods to an existing interface as a shortcut
Adding a new method to a widely-used interface is tempting when you only need one implementation to have that method. The correct move is a new, narrower interface. The shortcut forces every other implementation to handle a method it didn’t need.
4. Confusing ISP with SRP
ISP is about clients and the interfaces they depend on. SRP is about classes and why they change. An interface can be well-segregated from the client’s perspective while the implementing class still has too many responsibilities.
Related practices
Part of the PushBackLog Best Practices Library. Suggest improvements →