Exercises — SOLID — Interface Segregation Principle
This page assumes only the parent note: the ISP topic note. Every term used below is either defined here or re-earned below.
Vocabulary we will lean on (re-earned here)
Before any exercise, let us nail down three words this page uses constantly, so nothing is "assumed from the parent note."
The picture below is the mental model for the whole page — keep it in mind for every exercise.

Level 1 — Recognition
Goal: spot an ISP violation on sight, and name the vocabulary.
Exercise 1.1
Which single sentence is the Interface Segregation Principle?
- (a) A class should have only one reason to change.
- (b) No client should be forced to depend on methods it does not use.
- (c) High-level modules should not depend on low-level modules.
- (d) Subtypes must be substitutable for their base types.
Recall Solution
(b). Option (a) is the Single Responsibility Principle, (c) is the Dependency Inversion Principle, and (d) is the Liskov Substitution Principle. ISP is specifically about what a client is forced to depend on.
Exercise 1.2
Here is a class. Circle the "smell" that tells you an interface above it is too fat.
class OldPrinter implements Machine {
public void print(Doc d) { /* real work */ }
public void scan(Doc d) { throw new UnsupportedOperationException(); }
public void fax(Doc d) { throw new UnsupportedOperationException(); }
}Recall Solution
The smell is the ==throw new UnsupportedOperationException()== inside methods scan and fax. OldPrinter was forced to implement abilities it does not have. Those two stubbed methods are the fingerprint of a fat interface (Machine) that bundled unrelated roles — two forced dependencies, exactly the orange arrows from the figure above.
Exercise 1.3
Match each word to its meaning.
- Client 2. Fat interface 3. Role interface
- (A) A small interface holding only the cohesive methods one kind of caller needs.
- (B) Any code that depends on an interface — the caller and the implementer.
- (C) One interface bundling many unrelated capabilities.
Recall Solution
1→B, 2→C, 3→A. Client is the subtle one: it is not only the code that calls a method. The implementing class is also a client of the interface, because it too is "forced to depend" on every declared method.
Level 2 — Application
Goal: split a concrete fat interface, and count the win.
Exercise 2.1
Given the fat interface below, produce the segregated interfaces and rewrite OldPrinter and AllInOne.
interface Machine {
void print(Doc d);
void scan(Doc d);
void fax(Doc d);
}
// OldPrinter only prints. AllInOne does everything.Recall Solution
Split by role — one interface per cohesive capability:
interface Printer { void print(Doc d); }
interface Scanner { void scan(Doc d); }
interface Fax { void fax(Doc d); }
class OldPrinter implements Printer {
public void print(Doc d) { /* real work */ }
}
class AllInOne implements Printer, Scanner, Fax {
public void print(Doc d) { ... }
public void scan(Doc d) { ... }
public void fax(Doc d) { ... }
}Why: OldPrinter's contract is now honest — it advertises only print. A class needing several roles simply implements several small interfaces (composition of roles). This is the right-hand side of the figure above.
Exercise 2.2
Using the parent note's formula for forced (unused) method dependencies,
where is the number of methods in the fat interface, the number of implementers, and the count of methods implementer genuinely uses — compute for a Machine interface with methods and five implementers whose genuine usage counts are .
Recall Solution
Here . Plug each implementer in: So the fat interface forces 5 dead method dependencies. After perfect segregation each implementer pulls in exactly its , so — those 5 vanish.
The stacked bar chart below makes this concrete: for each implementer the teal block is the methods it uses and the orange block on top is the forced gap . The total orange height across all five bars is .

Exercise 2.3
A canteen scheduler only ever needs to feed workers. After you segregate Worker into Workable and Feedable, what type should the scheduler depend on, and why does this delete a whole class of runtime bugs?
Recall Solution
It should depend only on ==Feedable==.
Why the bug disappears: a RobotWorker implements only Workable, so it is not a Feedable. The scheduler cannot even reference a robot — the type system rejects scheduler.feed(robot) at compile time. The old bug (robot.eat() throwing at runtime) is now impossible to write.
Level 3 — Analysis
Goal: reason about which principle is violated, and why.
Exercise 3.1
A teammate says: "Stubbing scan() with throw new UnsupportedOperationException() is fine — the class still implements the interface, so ISP is satisfied." Diagnose exactly which SOLID principle(s) this breaks and explain the mechanism.
Recall Solution
It breaks two:
- ISP, because the class was forced to depend on a method (
scan) it does not use — the stub is the very symptom ISP warns about. - LSP, because a caller holding a
Machinereference expectsscan()to work. Substituting anOldPrinterfor a generalMachinebreaks that expectation at runtime. The subtype does not honour the supertype's contract.
"It compiles" is not the same as "it is substitutable." A compile-time stub is a runtime bomb. The correct fix is to remove scan from the interface OldPrinter implements — i.e. segregate.
Exercise 3.2
Is the following a violation of SRP, ISP, or both? Justify by pointing at "reasons to change" vs "forced dependency."
// One class, one responsibility (managing a user account),
// but it exposes a giant interface to very different callers.
interface UserAccount {
// used by the login screen
boolean checkPassword(String p);
// used by the admin dashboard
void banUser();
// used by the billing system
Invoice generateInvoice();
}Recall Solution
This is primarily an ISP problem, and not necessarily an SRP problem.
- SRP asks: does the class have more than one reason to change? Possibly not — "managing an account" can be argued as one responsibility.
- ISP asks: are clients forced to depend on methods they don't use? Yes. The login screen only needs
checkPassword, yet it depends on a type that also declaresbanUserandgenerateInvoice. ChangegenerateInvoice's signature and the login screen recompiles for no reason.
Fix: split into role interfaces Authenticatable, Moderatable, Billable, each handed to the client that needs it. This shows ISP is "SRP for interfaces"—about callers, not about the class's internal reasons to change.
Exercise 3.3
Recompute the forced-dependency count as a saving. A fat interface has methods and implementers with usage . How many forced dependencies does segregation eliminate, and what fraction of all declared obligations were dead weight? (Total declared obligations .)
Recall Solution
Forced dependencies: Total declared obligations . Fraction that was dead weight: Half of every "must implement" here was a lie removed by ISP.
Why every term is (the fact): counts methods the implementer uses out of the interface. You cannot use a method that is not there — the interface only offers of them — so at most , i.e. for every . Hence each gap is never negative: forced dependencies can only add, never subtract.
Level 4 — Synthesis
Goal: combine ISP with other principles and patterns.
Exercise 4.1
You have a legacy LegacyReporter class with a huge public API (12 methods). New code should depend only on a 2-method Summariser role you define. You cannot modify LegacyReporter. Which design pattern from the connections list lets you present the small role interface to new clients, and sketch how.
Recall Solution
Use the Adapter pattern. Define the segregated role interface, then wrap the fat legacy class:
interface Summariser {
String headline();
int total();
}
class ReporterAdapter implements Summariser {
private final LegacyReporter legacy;
ReporterAdapter(LegacyReporter legacy) { this.legacy = legacy; }
public String headline() { return legacy.buildFullReport().getTitle(); }
public int total() { return legacy.computeGrandTotal(); }
}Why this synthesises ISP + Adapter: new clients depend only on the thin Summariser (ISP satisfied), while the adapter absorbs the fat legacy API behind the scenes. The adapter is precisely "a translator between a segregated interface and an un-segregatable class."
Exercise 4.2
Explain how ISP feeds Dependency Inversion. Specifically: why is a small role interface a better abstraction to "depend on the abstraction, not the concretion" than a fat one?
Recall Solution
DIP says high-level modules should depend on abstractions. The quality of that abstraction matters:
- A fat abstraction re-imports all the coupling DIP was trying to break — you inverted the dependency arrow but still depend on 12 methods, so any of the 12 changing ripples up.
- A small role interface exposes only what the high-level module needs, so the inverted dependency is minimal and stable.
In short: ISP makes the abstractions DIP depends on thin and stable. ISP + DIP together give you both "point at an interface" and "point at a tiny one."
Exercise 4.3
Show, with the formula, that segregation into role interfaces can never increase forced dependencies. That is, argue always, and state when equality holds.
Recall Solution
Under perfect segregation each implementer implements exactly the interfaces whose methods it uses, so it is forced to supply exactly methods and no more. Its forced (unused) count is . Summing: Since every term of has (you cannot use more methods than exist), each term is , so . Equality () holds only when every implementer already uses all methods ( for all ) — i.e. the interface was never fat to begin with, so there is nothing to segregate.
Level 5 — Mastery
Goal: optimise, generalise, and defend edge cases.
Exercise 5.1
Generalise the win metric. A team keeps a fat interface but adds one new method to it. There are implementers, and only of them genuinely need the new method (). Let denote the change in forced dependencies caused by this single addition (i.e. new value of minus the old value). Find and say what it reveals about the cost of change for fat interfaces.
Recall Solution
Define = (forced dependencies after the addition) − (before). The new method raises by 1, and every implementer must now supply it — but only of them use it. The other gain one forced dependency each, while the that use it gain zero. Therefore Interpretation: one line added to a fat interface imposes new dead implementations — the cost of a change grows with the number of unrelated implementers. With segregation the new method lives in its own role interface that only the needers implement, so . This is the mechanical reason "a fat interface has many reasons to change / many things to break."
Exercise 5.2
Design edge case: an interface Stream has read() and write(). You discover read-only files exist. A colleague proposes splitting into Readable and Writable. Another says "no — keep Stream, they're cohesive." Give the decision rule and resolve it.
Recall Solution
Decision rule: split when there exists at least one real client that uses one method but is forced into the other. Cohesion is measured by how clients use the methods together, not by how related they sound.
- Read-only files are a concrete client that needs
read()but must not be forced to implementwrite(). That existence proof means the methods are not cohesive for all clients → split intoReadableandWritable. - A read-write file simply implements both (
class RWFile implements Readable, Writable).
So the second colleague is wrong because a divergent client exists. Had every stream always been read-write, keeping Stream would be correct (over-splitting is the L2 trap).
Exercise 5.3 (Degenerate & vacuous cases)
Evaluate in each edge case and say what it means. Pay special attention to the vacuous cases where a sum has no terms.
- (a) (interface has zero implementers — the vacuous case).
- (b) (interface declares zero methods — a marker interface).
- (c) Every .
- (d) Every .
Recall Solution
- (a) (vacuous): the sum has no terms, so by the convention that an empty sum equals , . An interface nobody implements forces nothing — it is harmless (though possibly dead code). This vacuous truth matters: "there are no implementers to force" is why the metric is zero, not because usage was perfect.
- (b) : each term is , but forces , so every term is → . A marker interface with no methods can force nothing.
- (c) all : every term → . The interface is perfectly cohesive; ISP has nothing to fix.
- (d) all : every term → , the maximum. Every implementer is dead weight in every method — the worst possible fat interface, where nobody uses anything they're forced to implement.

How ISP sits among the other SOLID principles
Mastery means knowing not just ISP but where it touches its neighbours. Read this map like a set of quadrants — each edge is "ISP hands off to / is confused with" another principle.
Recall How to read the map
- ISP → SRP: same "keep it small" spirit, but SRP judges a class's reasons to change, ISP judges what a caller is forced to depend on (Exercise 3.2).
- ISP → LSP: a forced stub that throws is exactly the LSP-breaking substitution (Exercise 3.1).
- ISP → DIP: thin role interfaces are the best abstractions to invert onto (Exercise 4.2).
- ISP → Cohesion: segregation groups methods by cohesive role, raising cohesion and cutting accidental coupling (L2 trap).
Score yourself
Recall What mastery looks like
Connections
- Parent topic — ISP
- SOLID — Single Responsibility Principle — L3.2 contrasts the two.
- SOLID — Liskov Substitution Principle — L3.1's stub breaks it.
- SOLID — Dependency Inversion Principle — L4.2 shows ISP feeds it.
- Cohesion and Coupling — the "split by role" decision rule.
- Design Patterns — Adapter — L4.1 quarantines the fat legacy API.