Interface Segregation
Interface Segregation splits fat interfaces so clients depend only on methods they use.
Introduction
Interface Segregation splits fat interfaces so clients depend only on methods they use. A forty-method repository forces every mock to stub forty methods when the test needs two. ISP creates role interfaces — Reader, Writer, Search — so consumers declare minimal dependencies.
The story
A RepositoryBase had twenty-six methods. Mocking for one read test required stubbing twenty-six methods. After ISP split into Reader, Writer, and Search, test setup time dropped 70%.
The business problem
Fat interfaces tax every consumer and test:
- Mock bloat — stub methods never called.
- Recompile ripple — unused method changes rebuild unrelated clients.
- LSP traps — implementations throw NotSupported on irrelevant methods.
- Hidden dependencies — consumers coupled to capabilities they don't use.
The problem teams faced
Signs you need ISP:
- Interface exceeds ~5–7 methods with distinct client groups.
- Mocks require many `jest.fn()` stubs per test.
- Implementations throw "not supported" for interface methods.
Understanding the topic
Role interfaces — small, cohesive, client-specific:
- Role — Reader, Writer, Authenticator, Searcher.
- Implementer — one class may implement multiple roles.
- Client — depends on role type, not fat base.
Internal architecture
Before / after:
Before: OrderRepository (26 methods) ← all services depend on allAfter: OrderReader ← query servicesOrderWriter ← command handlersOrderSearch ← admin dashboardPostgresOrders implements all three
Visual explanation
ISP in three diagrams:
Informative example
User service ISP:
// ❌ Fat interfaceinterface UserService {getById(id: string): User;updateProfile(u: User): void;resetPassword(id: string): void;listAll(): User[];delete(id: string): void;exportCsv(): Buffer;}// ✅ Role interfaces — clients depend on slicesinterface UserReader { getById(id: string): User; }interface UserWriter { updateProfile(u: User): void; resetPassword(id: string): void; }interface UserAdmin { listAll(): User[]; delete(id: string): void; exportCsv(): Buffer; }class ProfileController {constructor(private users: UserReader & UserWriter) {} // only what it needs}
Execution workflow
Map clients to methods
Which service calls which methods?
Real-world use
AWS SDK v3 redesigned around ISP — import only the commands you use. Spring Data splits read/write repositories. ISP + SRP together: roles for clients, focused classes for implementers.
Production case study
Twenty-six-method repository (above): test setup −70%, mock-related test failures eliminated.
Trade-offs
- Smaller mocks and clearer dependencies.
- More interface files to navigate.
- Over-splitting creates micro-interface noise.
Decision framework
- Split when interface >5 methods with distinct client groups.
- Split when mock setup pain appears in tests.
- Keep fat interface when every client truly uses every method (rare).
Best practices
- Name roles by client capability: Reader, Writer, Notifier.
- One concrete class can implement many roles.
- If implementer throws on role methods, split was wrong — revisit SRP on class.
Anti-patterns to avoid
- One-method interfaces for every function — noise without cohesion.
- ISP split without updating client constructor types.
Common mistakes
- Class implementing twelve roles — class itself may violate SRP.
Debugging tips
- Count mock stubs per test — >5 unused stubs suggests ISP opportunity.
Optimization strategies
- CQRS read/write split when read and write paths scale differently.
Common misconceptions
- ISP ≠ one method per interface. Cohesive role with 3–5 related methods is fine.
Advanced interview questions
Interview Prep
Practice concise answers, then expand each card for the explanation.
1AdvancedQuestionISP vs CQRS?+
Answer
Follow-up
2IntermediateQuestionWhen is fat interface OK?+
Answer
Follow-up
Summary
You can split fat interfaces into roles, shrink test mocks, and relate ISP to CQRS. Tell the twenty-six-method repository story.