Skip to main content
Articles/architecture/interface-segregation-principle
Series: Low-Level Design Interview Prepβ€’Part 4 of 17
Scenario Article

Interface Segregation Principle: No Fat Interfaces

The Fat Remote Control Problem

Abstract Algorithmsβ€’Mar 9, 2026
XLinkedInReddit
πŸ’‘ Executive Summary

Imagine a TV remote with 50 buttons.

🎯 System Scenario & Requirements

System Goal

Imagine a TV remote with 50 buttons.

Actors & Components

  • Application or API tier
  • Core service under design
  • Operator or SRE observer

Functional Requirements

  • Imagine a TV remote with 50 buttons.
  • You only ever use 5: power, volume, channel up/down, and input select.
  • ISP says: don't give every device the full remote .

Constraints & Assumptions

  • ISP is closely related to: Rule of thumb: If you ask "when would a class mock or stub this method just to satisfy the interface?", that method doesn't belong in that interface.
  • When Fat Interfaces Are Acceptable πŸ“Š ISP Decision Flow: When to Split an Interface The flowchart captures the single question that drives every ISP refactoring decision: do all clients use all the methods?

⚑ Execution Stages & Decision Trace

1

Stage 1 β€” The Classic Violation: The Printer Interface

Action / Decision:

SimplePrinter can only print β€” but the Machine interface forces it to "implement" 4 other operations it can't do.

Component Behavior:

Any code that calls simplePrinter.fax() fails at runtime rather than at compile time.

Observable Output: πŸ“Š Fat Interface Violation: SimplePrinter Forced to Stub Four Methods Both SimplePrinter and OfficePrinter implement the fat Machine interface, but the similarity ends there.
Read Full Stage in Deep Dive ↓
πŸ“–

Deep Dive

Detailed Architectural Analysis

Scenario context

Imagine a TV remote with 50 buttons. You only ever use 5: power, volume, channel up/down, and input select. ISP says: don't give every device the full remote. Give each device the buttons it actually needs.

In software, a fat interface forces every implementing class to provide methods it doesn't need, creating:

  • Stub implementations that throw UnsupportedOperationException.
  • Tight coupling β€” changes to…

This walkthrough focuses on The Fat Remote Control Problem.

Before making a design choice, clarify:

  • Imagine a TV remote with 50 buttons.
  • You only ever use 5: power, volume, channel up/down, and input select.
  • ISP says: don't give every device the full remote .

Concepts in play: design-principles-solid, jvm-internals-and-garbage-collection

Stage 1 β€” The Classic Violation: The Printer Interface

  • SimplePrinter can only print β€” but the Machine interface forces it to "implement" 4 other operations it can't do.
  • Any code that calls simplePrinter.fax() fails at runtime rather than at compile time.
  • πŸ“Š Fat Interface Violation: SimplePrinter Forced to Stub Four Methods Both SimplePrinter and OfficePrinter implement the fat Machine interface, but the similarity ends there.
  • OfficePrinter can legitimately fulfil all five methods, while SimplePrinter is forced to throw UnsupportedOperationException in scan() , fax() , copy() , and staple() β€” four methods it cannot meaningfully support.
public interface Machine {
    void print(Document d);
    void scan(Document d);
    void fax(Document d);
    void copy(Document d);
    void staple(Document d);
}

public class SimplePrinter implements Machine {
    public void print(Document d) { /* works */ }
    public void scan(Document d)  { throw new UnsupportedOperationException(); }
    public void fax(Document d)   { throw new UnsupportedOperationException(); }
    public void copy(Document d)  { throw new UnsupportedOperationException(); }
    public void staple(Document d){ throw new UnsupportedOperationException(); }
}
classDiagram
    class Machine {
        <>
        +print(d)
        +scan(d)
        +fax(d)
        +copy(d)
        +staple(d)
    }
    class SimplePrinter {
        +print(d)
        +scan(d) throws
        +fax(d) throws
        +copy(d) throws
        +staple(d) throws
    }
    class OfficePrinter {
        +print(d)
        +scan(d)
        +fax(d)
        +copy(d)
        +staple(d)
    }
    Machine <|.. SimplePrinter
    Machine <|.. OfficePrinter

Observable outcome: OfficePrinter can legitimately fulfil all five methods, while SimplePrinter is forced to throw UnsupportedOperationException in scan() , fax() , copy() , and staple() β€” four methods it cannot meaningfully support.

Stage 2 β€” Applying ISP: Split by Role

  • After the split, SimplePrinter implements only Printable β€” the single capability it genuinely supports β€” while OfficePrinter implements Printable , Scannable , and Faxable to reflect its real hardware capabilities.
  • Any code holding a Printable reference can safely accept either class, and no class is ever forced to provide a method it cannot meaningfully execute.
  • Now: - SimplePrinter compiles and works with zero stubs.
    • OfficePrinter implements everything it genuinely supports.
// Segregated interfaces β€” each is a single capability
public interface Printable  { void print(Document d); }
public interface Scannable  { void scan(Document d); }
public interface Faxable    { void fax(Document d); }
public interface Copyable   { void copy(Document d); }
public interface Stapleable { void staple(Document d); }

// Simple printer: only what it can do
public class SimplePrinter implements Printable {
    public void print(Document d) { /* full implementation */ }
}

// All-in-one office printer: combines all
public class OfficePrinter implements Printable, Scannable, Faxable, Copyable, Stapleable {
    public void print(Document d)  { /* ... */ }
    public void scan(Document d)   { /* ... */ }
    public void fax(Document d)    { /* ... */ }
    public void copy(Document d)   { /* ... */ }
    public void staple(Document d) { /* ... */ }
}
classDiagram
    class Printable {+print(d)}
    class Scannable {+scan(d)}
    class Faxable   {+fax(d)}

    SimplePrinter ..|> Printable
    OfficePrinter ..|> Printable
    OfficePrinter ..|> Scannable
    OfficePrinter ..|> Faxable

Observable outcome: - OfficePrinter implements everything it genuinely supports.

Stage 3 β€” Deep Dive: Python Protocol-Based ISP

  • Python uses structural subtyping via Protocol (PEP 544) β€” no explicit implements required: SimplePrinter never declared it implements Printable .
  • The type checker confirms structural compatibility at analysis time without runtime overhead.
  • Notice that OfficePrinter satisfies both Printable and Scannable β€” Python's Protocol lets you express the same ISP split as Java's explicit interfaces, but with zero inheritance boilerplate.
from typing import Protocol

class Printable(Protocol):
    def print(self, doc: str) -> None: ...

class Scannable(Protocol):
    def scan(self) -> str: ...

class SimplePrinter:
    def print(self, doc: str) -> None:
        print(f"Printing: {doc}")

class OfficePrinter:
    def print(self, doc: str) -> None:
        print(f"Printing: {doc}")
    def scan(self) -> str:
        return "scanned content"

def send_to_printer(device: Printable, doc: str) -> None:
    device.print(doc)

def run_scanner(device: Scannable) -> str:
    return device.scan()

send_to_printer(SimplePrinter(), "Invoice.pdf")   # OK β€” only print needed
send_to_printer(OfficePrinter(), "Report.pdf")    # OK β€” also satisfies Printable
run_scanner(OfficePrinter())                      # OK β€” satisfies Scannable
# run_scanner(SimplePrinter())                   # Type error β€” SimplePrinter has no scan()

Observable outcome: Notice that OfficePrinter satisfies both Printable and Scannable β€” Python's Protocol lets you express the same ISP split as Java's explicit interfaces, but with zero inheritance boilerplate.

Stage 4 β€” ISP Interface Segregation Flow

  • The transformation from a fat interface to segregated role-based interfaces β€” and how implementations choose only what they need.
  • The left-to-right flow traces the refactoring decision for the Machine interface.
  • OfficePrinter answers "YES" to supporting all capabilities and takes the direct path β€” it implements Printable , Scannable , and Faxable because it genuinely provides all three.
  • SimplePrinter answers "NO" and routes through the split, arriving at Printable alone, with no stubs and no forced coupling to operations its hardware cannot perform.
flowchart LR
    A[Machine fat interface] --> B{Does class support all?}
    B -- YES --> C[OfficePrinter implements all 5]
    B -- NO --> D[Split into role interfaces]
    D --> E[Printable]
    D --> F[Scannable]
    D --> G[Faxable]
    E --> H[SimplePrinter implements Printable only]
    C --> E
    C --> F
    C --> G

Observable outcome: SimplePrinter answers "NO" and routes through the split, arriving at Printable alone, with no stubs and no forced coupling to operations its hardware cannot perform.

Stage 5 β€” Real-World Application: ISP in Production Systems

  • ISP applies everywhere interfaces define contracts between components.
  • Kubernetes analogy: A Pod spec in Kubernetes follows ISP β€” you only declare the capabilities you need (volumes, network policies, security contexts).
  • Each concern is a separate, composable specification.
  • You don't get a monolithic "configure everything" spec that forces you to set all 200 fields.

Observable outcome: You don't get a monolithic "configure everything" spec that forces you to set all 200 fields.

Stage 6 β€” Hands-On: Segregate a Fat Interface

  • Here is a fat IUserService interface.
  • Problem: A read-only admin dashboard must implement createUser , deleteUser , sendWelcomeEmail .
  • A background job that only resets passwords must implement findById , listAll , sendWelcomeEmail , and getAuditLog .
  • ISP-compliant split: Key benefit: AdminDashboardService cannot accidentally call deleteUser β€” the interface doesn't include it.
// πŸ›‘ Fat interface β€” forces all implementers to support everything
public interface IUserService {
    User findById(String id);
    List<User> searchByName(String name);
    List<User> listAll();
    void createUser(User user);
    void updateUser(User user);
    void deleteUser(String id);
    void resetPassword(String userId, String newPassword);
    void sendWelcomeEmail(String userId);
    List<String> getAuditLog(String userId);
}
public interface IUserReader {
    User findById(String id);
    List<User> searchByName(String name);
    List<User> listAll();
}

public interface IUserWriter {
    void createUser(User user);
    void updateUser(User user);
    void deleteUser(String id);
}

public interface IUserPasswordManager {
    void resetPassword(String userId, String newPassword);
}

public interface IUserNotifier {
    void sendWelcomeEmail(String userId);
}

public interface IUserAuditor {
    List<String> getAuditLog(String userId);
}

// Admin dashboard only needs reads
public class AdminDashboardService implements IUserReader { ... }

// Full user management service implements all
public class UserManagementService implements IUserReader, IUserWriter,
    IUserPasswordManager, IUserNotifier, IUserAuditor { ... }

Observable outcome: ISP-compliant split: Key benefit: AdminDashboardService cannot accidentally call deleteUser β€” the interface doesn't include it.

End-to-end outcome

After working through the stages, you should be able to explain:

    • UnsupportedOperationException is the ISP alarm.
  • Any time you write throw new UnsupportedOperationException() in an interface implementation, that method doesn't belong in that interface.

Alternatives considered

Decision Guide

Decision Guide

Failure and degraded modes

  • Detection: Rule of thumb: If you ask "when would a class mock or stub this method just to satisfy the interface?", that method doesn't belong in that interface.
  • User or operator impact: Rule of thumb: If you ask "when would a class mock or stub this method just to satisfy the interface?", that method doesn't belong in that interface.

When Fat Interfaces Are Acceptable

  • Detection: πŸ“Š ISP Decision Flow: When to Split an Interface The flowchart captures the single question that drives every ISP refactoring decision: do all clients use all the methods?
  • User or operator impact: πŸ“Š ISP Decision Flow: When to Split an Interface The flowchart captures the single question that drives every ISP refactoring decision: do all clients use all the methods?

Share this case study

Discuss architecture trade-offs and lessons with your team.

Share:XLinkedInReddit