Interface Segregation Principle
Prefer many small, focused interfaces over one large general-purpose one — clients should not be forced to depend on methods they don't use.
★★★★★3/5Inside a codebase — classes, modules, files
How it works
The Interface Segregation Principle (ISP), part of SOLID (Robert C. Martin, 1994), says that no client should be forced to depend on methods it does not use. Fat interfaces force implementors to stub out methods that don't apply to them — a sign the interface models the wrong abstraction.
The canonical violation: a Worker interface with work(), eat(), and sleep(). A Robot implements Worker but must throw NotImplemented on eat() and sleep() — methods that make no sense for it.
The fix: split into Workable, Feedable, and Restable. Human implements all three. Robot implements only Workable. No stubs, no surprises.
ISP also applies to clients: if a caller only needs read operations, it shouldn't depend on an interface that also declares write operations — because any change to the write signature forces a recompile/retest of the reader.
Small interfaces are easier to implement, easier to mock in tests, and easier to compose. They also age better: adding a method to a small, focused interface affects fewer implementors.
Implementation
TypeScript · Go · Rust// ❌ Fat interface — Robot must stub methods it can never use
interface Worker {
work(): void;
eat(): void;
sleep(): void;
}
class HumanWorker implements Worker {
work() { console.log("working"); }
eat() { console.log("eating"); }
sleep() { console.log("sleeping"); }
}
class Robot implements Worker {
work() { console.log("working"); }
eat() { throw new Error("Robots do not eat"); } // ❌ forced stub
sleep() { throw new Error("Robots do not sleep"); }
}
// ✓ Segregated interfaces — each client depends only on what it uses
interface Workable { work(): void; }
interface Feedable { eat(): void; }
interface Restable { sleep(): void; }
class HumanWorker implements Workable, Feedable, Restable {
work() { console.log("working"); }
eat() { console.log("eating"); }
sleep() { console.log("sleeping"); }
}
class Robot implements Workable {
work() { console.log("working"); } // ✓ no stubs, no surprises
}Why it matters
Fat interfaces create unnecessary coupling. Classes implementing methods they don't use, test doubles mocking operations they'll never call, and callers recompiling because an unrelated method changed — all signs of ISP violation.
✓ When to use
- →When an interface has methods that some implementors must stub with NotImplemented
- →When a client imports an interface but only uses two of its ten methods
- →When splitting an interface would let different teams evolve their parts independently
- →When designing public library APIs — lean interfaces are more stable
✗ When NOT to use
- →Over-segregation creates too many tiny interfaces that must be composed manually
- →A cohesive group of methods that always travel together belongs in one interface
Trade-offs
Clients depend only on what they use — changes elsewhere don't affect them
Too many fine-grained interfaces become hard to discover and compose
Implementors only implement what's relevant — no forced stubs
Splitting existing interfaces is a breaking change for existing implementors
Interfaces are more stable — a method change affects fewer clients
Finding the right granularity requires domain knowledge and experience
In production
io.Reader (one method), io.Writer (one method), io.ReadWriter (both) — compose interfaces rather than one large one
CrudRepository, PagingAndSortingRepository, JpaRepository — progressively richer interfaces; use the smallest that fits
useState, useEffect, useRef — each hook is a tiny, focused 'interface' to a single React capability
Industry adoption
Related principles
Interface / Contract
Define what a component must do without dictating how it does it — so implementations can vary freely while callers remain stable.
Single Responsibility Principle
A class should have only one reason to change — keep each unit focused on a single job so unrelated concerns don't accidentally break each other.
Dependency Inversion Principle
High-level modules should not depend on low-level modules — both should depend on abstractions, inverting the conventional dependency direction.
Abstraction
Reduce complexity by modelling only the details that matter for your problem — hide everything else behind a stable interface.