Back to the map

Module 02 · SOLID and friends · Lesson 4 of 7

Interface segregation

No client should be forced to depend on methods it does not use. Why fat interfaces couple unrelated clients and force fake implementations, how to split them into role interfaces shaped by their callers, and where to stop.

CoreShould-knowMid 10 min Updated Nov 2026

Why it matters

A single large interface that every part of the system uses looks tidy and behaves badly. A reporting module that only reads orders still depends on methods for cancelling and refunding them; when those methods change, the reporting module recompiles and its tests need fakes with twenty stubbed methods. Implementations that cannot support part of the interface start throwing, which is the Liskov violation from the previous lesson. The interface segregation principle keeps dependencies as narrow as the clients' actual needs.

The idea

No client should be forced to depend on methods it does not use. Instead of one interface shaped by the implementation (everything the order store can do), offer several role interfaces shaped by the clients (what the reporting module reads, what the checkout writes, what the admin tool manages). One class can implement all of them; each client depends only on its own.

fat interface«OrderStore»find · search · save · cancel · refund · purgeReportsCheckoutAdminrole interfaces«OrderReader»find, search«OrderWriter»save, cancel«OrderAdmin»refund, purgeSqlOrderStoreimplements all three
One fat interface ties every client to every method. Role interfaces let each client depend on exactly what it uses, while one class still implements them all.
interface OrderReader { Optional<Order> find(OrderId id); List<Order> search(OrderQuery q); }
interface OrderWriter { void save(Order order); void cancel(OrderId id, Reason reason); }
interface OrderAdmin  { void refund(OrderId id, Money amount); void purge(Instant before); }

final class SqlOrderStore implements OrderReader, OrderWriter, OrderAdmin { ... }

final class RevenueReport {
    private final OrderReader orders;                 // cannot cancel or purge, even by accident
    RevenueReport(OrderReader orders) { this.orders = orders; }
}

What it buys

BenefitWhy
Smaller blast radiusChanging the admin methods does not touch reporting code or its tests
Simpler test doublesA fake OrderReader needs two methods, not twenty
Honest capabilitiesA read-only replica implements OrderReader only; nobody calls save on it and gets an exception
Clearer intent and least privilegeA constructor taking OrderReader documents and enforces that the class only reads

Shape interfaces by their callers

The most reliable way to get segregation right is to let the consumer define the interface it needs. Go makes this idiomatic: interfaces are declared next to the code that uses them and satisfied implicitly. In Java or C#, the same effect comes from asking, for each new client, "which methods does this actually call?" and declaring an interface of exactly those, rather than reusing the biggest one available.

Where to stop

Splitting every method into its own interface creates a fog of tiny types and constructors with ten parameters. Group methods that are used together by the same kind of client. Two to five methods per role interface is common; one-method interfaces are fine when they represent a genuine role (a Clock, a Notifier, a strategy).

Trade-offs

Role interfaces cost more type declarations and a moment's thought about which one each client needs. They pay back with narrower coupling, simpler tests and capabilities that cannot be misused. A single fat interface is acceptable inside a small module with one client; the principle matters most at module boundaries and for interfaces with many implementers or clients.

In an interview

Apply it where a component plainly only reads: "The fare display only needs to read prices, so it depends on a PriceReader, not the full PricingService with its admin and override methods. That also means the display can be backed by a read replica." It shows you size dependencies to the client.

Check yourself

A Machine interface declares print, scan, fax and staple. A BasicPrinter implementation throws for scan, fax and staple, and the print-queue tests need fakes for all four methods. Which change best follows the interface segregation principle?

Interface segregation · Backplane