Worked examples — SOLID — Interface Segregation Principle
Before we start, we reuse one tool from the parent — the forced-dependency count. Let me re-earn every symbol so a newcomer can read line one.
The bar chart below shows exactly what these two numbers look like. Each implementer is a column. Below the dashed yellow line at we colour the green portion (methods genuinely used, ) and stack the red portion on top (the forced gap ). The total red height across all columns is . The blue column beside each one is the segregated world: only the green used-portion remains, no red at all — that is . Look at how the two single-purpose machines carry tall red blocks while the all-capable AllInOne has none: fat interfaces punish the narrow clients.

The scenario matrix
Every problem this topic can throw at you falls into one of these cells. The worked examples below are tagged with the cell they cover. Two boundary cells (Z and Y) are the truly degenerate inputs — no implementers and no methods — and we discuss them right after the table.
| Cell | Case class | Trigger to watch for | Example |
|---|---|---|---|
| A | Classic fat split | one interface, mixed roles, a stub/throw | Ex 1 |
| B | Multi-role class | a class that legitimately needs many roles | Ex 2 |
| C | Zero forced weight (already good) | before you touch it — do nothing | Ex 3 |
| D | Degenerate / single implementer | , is splitting worth it? | Ex 4 |
| E | Over-splitting trap (exam twist) | methods always used together | Ex 5 |
| F | Real-world word problem | narrative, you extract | Ex 6 |
| G | Limiting behaviour | grows, or , or | Ex 7 |
| H | Interaction with LSP | a throw hides an ISP+LSP double fault | Ex 8 |
| Z | No implementers () | interface exists but nothing implements it | below |
| Y | Empty interface () | a marker interface with no methods | below |
The diagram below lays out the eight main cells as a grid so you can see the space you're covering rather than read it as a list — colour marks whether each cell ends at already (green) or needs a fix (red).

We now walk every remaining cell.
Worked Examples
Example 1 — Cell A: the classic fat split
- Identify . (
work,eat). implementers. Human uses both: . Robot only works: . Why this step? is defined purely from these three numbers, so we extract them first. - Compute the fat cost. .
Why this step? The single forced dependency is exactly the robot's fake
eat()— the number pins the smell to one method. - Segregate by role. Split into
Workable { work() }andFeedable { eat() }. Human implements both; Robot implements onlyWorkable. Why this step? Grouping by "who uses it together" removes the robot's obligation toeat(). - Compute the segregated cost. Now robot's interface has , ; human's roles each match its needs. . Why this step? Confirms the split actually removed the forced weight (didn't just move it).
Verify: , , so ISP removed forced dependency. The removed unit corresponds to the one
UnsupportedOperationExceptionwe deleted — units match (1 forced method = 1 stub).
Example 2 — Cell B: a class that needs many roles
- Extract numbers. . . Uses:
AllInOne,OldPrinter,Scanjet. Why this step? Same recipe — the formula only cares about these counts. - Fat cost. .
Why this step? Notice
AllInOnecontributes — a class using everything is never over-served. All forced weight comes from the single-purpose machines. This is the exact scenario drawn in figure s01. - Segregate.
Printer{print},Scanner{scan},Fax{fax}.AllInOne implements Printer, Scanner, Fax(multiple small interfaces — this is allowed and encouraged). Why this step? A class needing many roles simply composes them; ISP never forbids implementing several interfaces. - Segregated cost. Each class now implements exactly the roles it uses: . Why this step? Verifies composition removed all forced units.
Verify: , ; win . The multi-role class added to the fat cost, confirming that fat interfaces punish the narrow implementers, not the broad one.
Example 3 — Cell C: already good, , do nothing
- Numbers. , , every . Why this step? A one-method interface cannot over-serve anyone.
- Cost. . Why this step? means there is nothing to fix — ISP is already satisfied.
- Decision. Leave it. Splitting a single cohesive method would create an empty win and hurt readability. Why this step? ISP is a diagnosis tool, not a mandate to shred every interface.
Verify: before and after; interfaces after . Sanity check: you cannot make negative, so is optimal — nothing to do.
Example 4 — Cell D: degenerate single implementer ()
- Compute now. . Why this step? With one all-using client, there is currently no forced weight.
- Ask the future question. ISP is also about anticipated clients. If a read-only
ReportViewerwill soon need onlyload, it would be forced intosave/migrate/backup. Why this step? measures the present; ISP reasoning also weighs likely new clients — a degenerate present can hide a fat future. - Judgment. If a narrow client is realistically coming, pre-split into
Reader{load}andWriter{save;migrate;backup}. If not, leave it. Why this step? ISP balances real cohesion against speculative splitting — don't split for imaginary clients.
Verify: present ; hypothetical read-only client would raise to . The number quantifies exactly the future risk (3 forced methods), guiding the decision.
Example 5 — Cell E: the over-splitting trap (exam twist)
- Before. , say clients, both use both methods, so and . Then . Why this step? Cohesive methods used together give already — there was never any forced weight to remove.
- After naive split. Each client must now depend on both
ReaderandWriteranyway; nothing was over-served, so stays but you've added an interface. Why this step? Splitting cohesive methods creates "interface explosion" with no improvement — the classic ISP anti-pattern. - Correct call. Keep
Streamas one role, because cohesion, not minimal size, is the goal (see the parent mistake box). Relate to Cohesion and Coupling: high method cohesion ⇒ keep together. Why this step? ISP splits by client role, not by counting methods.
Verify: and — the split changed nothing measurable but added interface. Zero benefit, positive cost ⇒ over-splitting is objectively worse.
Example 6 — Cell F: real-world word problem
- Read off . Basic , Plus , Elite . , . Why this step? Translate the story into the three numbers the formula needs.
- Fat cost. . Why this step? The 3 tells us Basic is dragged into 3 unused abilities — the biggest sufferer, as the narrowest client always is.
- Segregate by tier need.
Checkin{checkIn},Pool{usePool},Trainer{bookTrainer},Sauna{useSauna}. Basic implementsCheckin; Plus implementsCheckin, Pool; Elite implements all four. Why this step? Each tier composes exactly the roles it sells. - Segregated cost. . Why this step? Confirms all 5 forced units removed.
Verify: , , eliminated . Units: 5 forced methods removed across 3 classes; Basic accounted for 3 of them, matching "narrowest client suffers most."
Example 7 — Cell G: limiting behaviour
- Substitute. . Why this step? Every term is identical, so the sum is just copies of .
- Limit as . grows linearly in — add one method, add forced dependencies. Why this step? Shows why fat interfaces get catastrophically worse in big systems: cost scales with both size and client count.
- Opposite limit. If instead every (everyone uses everything), regardless of . Why this step? Confirms the degenerate "cohesive giant" boundary — big is fine iff every client uses all of it.
Verify: For : , matching Example 2's fat count. For : for any . Both boundary formulas check out.
Example 8 — Cell H: ISP meets the Liskov Substitution Principle
- ISP diagnosis.
Penguinuses of methods; forced dependency (fly), flagged by the throwing stub. Why this step? The stub is the ISP fingerprint — a forced method the client can't honestly provide. - LSP diagnosis. Any code holding a
Birdmay callfly()expecting flight; aPenguincrashes it. That breaks substitutability — the Penguin does not honour theBirdpromise. Why this step? A throwing stub is simultaneously an ISP smell and a Liskov Substitution Principle violation — one bad line, two broken principles. - Redesign. Split into
Eater{eat}andFlyer{fly}.Penguin implements Eater; aSparrow implements Eater, Flyer. Why this step? Removingflyfrom the penguin's contract fixes both faults at once, giving and restoring substitutability.
Verify: before (one stub); after . Both ISP (forced method gone) and LSP (no lying override) restored — one redesign, two checks pass.
Recall Self-test across the matrix
Which cell has and still might warrant splitting? ::: Cell D — single implementer, now, but a likely narrow future client justifies pre-splitting. In Example 7, what is when every client uses exactly one of methods? ::: . Why does over-splitting (Cell E) not improve ? ::: The methods are cohesive (always used together), so no client was ever over-served — was already . Which class contributes to in every example? ::: The one that uses all methods (). What is for an interface with no implementers () or no methods ()? ::: in both cases — an empty sum, or all-zero terms.
Connections
- SOLID — Interface Segregation Principle — this page is its worked-example deep dive.
- SOLID — Single Responsibility Principle — the " but maybe split" call (Cell D) is really SRP-style reasoning about future change.
- SOLID — Liskov Substitution Principle — Example 8 shows the throw-stub double fault.
- SOLID — Dependency Inversion Principle — the small role interfaces we produced are the ideal abstractions to depend on.
- Cohesion and Coupling — Cell E is decided entirely by method cohesion.
- Design Patterns — Adapter — when a legacy fat class must fit a segregated interface, an adapter bridges the gap.