SOLID Principles · The Five Principles

Interface Segregation Principle

Prefer small, role-based interfaces so no class is forced to implement - or depend on - methods it doesn't use.

  • 5 min read
  • 9 practice questions
  • 2 graded problems
On this page
  1. Description
  2. The principle
  3. How to apply it, step by step
  4. Where you'll see it
  5. Which tool for which job?
  6. Common mistakes
  7. Check yourself
  8. Key takeaways
  9. Quick revise
  10. Practice

Description

The Interface Segregation Principle says no client should be forced to depend on methods it doesn't use. In practice it means many small, role-shaped interfaces instead of one large one - so implementations don't have to fake capabilities they lack, and callers depend only on what they actually call. This skill covers how to find the roles in a fat interface, how to split it, and when splitting goes too far.

Here's the kind of interface it's about:

Java

interface Machine {
    void print(Document d);
    void scan(Document d);
    void fax(Document d);
}

class BasicPrinter implements Machine {
    public void print(Document d) { /* ... */ }
    public void scan(Document d) { throw new UnsupportedOperationException(); }
    public void fax(Document d)  { throw new UnsupportedOperationException(); }
}

Python

from abc import ABC, abstractmethod


class Machine(ABC):
    @abstractmethod
    def print(self, d: Document) -> None: ...

    @abstractmethod
    def scan(self, d: Document) -> None: ...

    @abstractmethod
    def fax(self, d: Document) -> None: ...


class BasicPrinter(Machine):
    def print(self, d: Document) -> None: ...

    def scan(self, d: Document) -> None:
        raise NotImplementedError

    def fax(self, d: Document) -> None:
        raise NotImplementedError

BasicPrinter is forced to "implement" things it can't do. And every class that only prints still depends on Machine - so a change to the fax method signature recompiles (and re-tests) code that never faxes.

We'll go from simple to more technical:

  1. The principle, and role interfaces in Java and Python.
  2. Splitting a fat interface by how its clients use it, step by step.
  3. Where you'll see role interfaces in real libraries.
  4. When to stop splitting.

The principle

Clients should not be forced to depend on methods they do not use.

Split fat interfaces into small, role-based ones. Classes implement only the roles they can fulfill; clients depend only on the roles they need.

Python's duck typing makes this the default: a function that only calls print() accepts any object with a print() method. A typing.Protocol writes that role down for type checkers - and the implementations don't have to inherit from it.

Java

interface Printer { void print(Document d); }
interface Scanner { void scan(Document d); }
interface Fax     { void fax(Document d); }

class BasicPrinter implements Printer { public void print(Document d) { ... } }

class OfficeMachine implements Printer, Scanner, Fax { ... }

class ReportService {
    private final Printer printer;        // needs printing only - and says so
    ReportService(Printer printer) { this.printer = printer; }
}

Python

from typing import Protocol


class Printer(Protocol):
    def print(self, d: Document) -> None: ...


class Scanner(Protocol):
    def scan(self, d: Document) -> None: ...


class Fax(Protocol):
    def fax(self, d: Document) -> None: ...


class BasicPrinter:                          # satisfies Printer just by having print()
    def print(self, d: Document) -> None: ...


class OfficeMachine:                         # satisfies all three
    def print(self, d: Document) -> None: ...

    def scan(self, d: Document) -> None: ...

    def fax(self, d: Document) -> None: ...


class ReportService:
    def __init__(self, printer: Printer):    # needs printing only - and says so
        self._printer = printer

How to apply it, step by step

  1. Look at an interface's clients: which methods does each one actually call?
  2. Group methods by the role they serve for those clients.
  3. Extract one small interface per role.
  4. Let implementations implement several roles if they can; let clients ask for just the role they use.
  5. Keep a combined interface only if some clients genuinely need everything.

Where you'll see it

  • The JDK's Readable, Appendable, Closeable, AutoCloseable, Comparable - tiny capability interfaces. (Python: collections.abc.Iterable, Sized, Container, Hashable - and "file-like objects", which only need the methods you call.)
  • Repositories split into read and write roles (OrderReader, OrderWriter), which also lets read-only services be handed a read-only view.
  • Spring Data offers CrudRepository and smaller repository interfaces so you expose only the operations you want.

Which tool for which job?

The situationDo this
Implementations throw "not supported" for some methodssplit the interface into roles they can each fulfill
A client calls two methods of a large interfacedepend on a small role interface with just those two
A function needs two roles at oncea combined interface, or an intersection (<T extends Printer & Scanner>); in Python, a Protocol inheriting both
A fat third-party interface you can't changedefine your own narrow role and adapt the third-party type to it
Some services must only read, never writehand them a read-only role (OrderReader)

Common mistakes

MistakeWhat happensDo this instead
One interface per methodevery class implements a dozen interfaces; clients need eight of themgroup methods by role
Splitting by guesswork, not by clientsroles nobody uses as a unitsplit by which methods each client calls
Default methods that throw "not supported"the fat interface, in disguisereal role interfaces
Role interfaces that mirror one class's public methods"header interfaces" that change whenever the class doesinterfaces shaped by what clients need

Check yourself

Try to answer before opening each one - recalling beats re-reading.

1. A Robot implements Worker { work(); eat(); } and throws on eat(). What's the ISP fix?

Show answer

Split into role interfaces such as Workable and Eatable; the robot implements only Workable, humans implement both.

2. A reporting service only reads orders but depends on a OrderRepository with 25 methods. Why does ISP care, even if the repository supports them all?

Show answer

The service is coupled to 25 methods it doesn't use - their changes affect it, and any test double must fake all of them. Depend on a small OrderReader role instead.

3. Should every method get its own interface?

Show answer

No - group methods by the role they play for their clients. One-method interfaces are great for true capabilities, not as a blanket rule.

Key takeaways

  • No class should implement - or depend on - methods it doesn't use.
  • Split fat interfaces into role interfaces based on how clients use them.
  • Classes can implement several roles; clients ask for exactly the role they need.
  • Avoid interface confetti; segregate by role, not by method.

Quick revise

The whole skill on one page, for the night before an interview.

Everything from the lesson and the deep dive on one page. Skim it before practice or an interview, and open the full lesson for anything that doesn't ring a bell.

Pick the tool

The situationDo this
Implementations fake some methodssplit into roles
A client uses a few methods of a big interfacedepend on a small role
Two roles needed togethercombined interface, <T extends A & B>, or a combined Protocol
A fat third-party interfaceyour own narrow role + an adapter
Read-only consumershand them a reader role

Snippets worth knowing by heart

Java

interface OrderReader { Optional<Order> findById(String id); List<Order> search(Query q); }
interface OrderWriter { void save(Order order); }

final class ReportService {
    private final OrderReader orders;               // can't write, even by mistake
    ReportService(OrderReader orders) { this.orders = orders; }
}

Python

class OrderReader(Protocol):
    def find_by_id(self, order_id: str) -> Order | None: ...
    def search(self, query: Query) -> list[Order]: ...

class ReportService:
    def __init__(self, orders: OrderReader):        # can't write, even by mistake
        self._orders = orders

The traps

  • Interface confetti - one interface per method.
  • Splitting by guesswork - split by how clients use it.
  • Default methods that throw - a fat interface in disguise.
  • Header interfaces - mirrors of one class, named IThing.

Staff-level points, one line each

  • Origin: Xerox's single Job class - every task was rebuilt when any one changed.
  • The dependency is the cost: recompiles, binary compatibility, test doubles for every method.
  • A hand-written double of a fat interface won't compile until it implements everything.
  • Mock(spec=Role) rejects calls the role doesn't have; a bare Mock() accepts anything.
  • Role interfaces (client-shaped) beat header interfaces (class-shaped).
  • The client owns the role - that's Dependency Inversion; "accept interfaces, return structs".
  • Narrow roles are least privilege (object capabilities); read/write split scales up to CQRS.
  • RandomAccess is a marker for an optional capability with a fallback - legitimate, unlike LSP-workaround type checks.

Say it in an interview

  • "Reporting only reads, so it gets an OrderReader - it can't write even by mistake."
  • "I'd define the role next to the code that uses it, and let the repository implement it."
  • "The test double uses the role as its spec, so calling anything else fails the test."
  • "For the vendor's 40-method client, I'd write a three-method role and an adapter."

Practice

This skill comes with 9 multiple-choice questions that tell you which misconception a wrong answer reveals and 2 graded problems. Your answers are graded and your mastery is tracked, so you'll know when the skill holds up. Running code and saving progress need a free account.

Practice Interface Segregation Principle free

Smart Home Capabilities

EasyFreeCoding problemJava · Python

A smart-home hub controls a lamp, a fan and a heater. It works out what each device can do by checking which small role interfaces it implements - Switchable, Dimmable, Heating - so it can reply "fan cannot dim" instead of sending a command the device can't handle.

A Read-Only View of the Order Store

MediumProCoding problemJava · Python

The order store's interface has been segregated into two roles: OrderReader for code that only reads, and OrderWriter for code that changes orders. The checkout gets the writer; reports and plugins get only a reader.

The full statement, the examples and the hidden test suite are part of AI Proof Engineers Pro.

Practice Interface Segregation Principle free

No card needed. Lessons, practice questions and easy problems are free forever.

Free plan, no card · 7-day Pro trial · Cancel anytime