Have you ever implemented an interface… only to realize you had to write empty methods just to make the compiler happy?
Or updated a shared interface… and suddenly, multiple unrelated classes started breaking?
If yes, you’ve probably encountered a violation of one of the most misunderstood design principles in software engineering: The Interface Segregation Principle (ISP).
Let’s understand it with a real-world example—and see why this principle helps you build cleaner, more focused code.
Imagine you're building a media player app that supports different types of media:
You might start with what feels like a convenient design: a single, unified interface that handles everything.
At first, it seems efficient. One interface, all capabilities. But as your app grows, problems start to show.
Let’s say you want to create a pure audio player—a class that should only handle sound:
Yikes.
Even though AudioOnlyPlayer
only needs audio methods, it’s forced to implement unrelated video functionality. You either throw exceptions or write empty methods. Neither is ideal.
The MediaPlayer
interface is doing too much. It combines multiple unrelated responsibilities:
This violates the Interface Segregation Principle (ISP).
Now, imagine you add a new method to the interface, like enablePictureInPicture()
. Suddenly, all existing implementations—audio-only, video-only, or otherwise—must update.
This tight coupling slows you down and increases the risk of bugs.
A client may expect any MediaPlayer
to support video, but passing in an AudioOnlyPlayer
will crash the program with an UnsupportedOperationException
.
That's a clear Liskov Substitution Principle (LSP) violation.
Clients should not be forced to depend on methods they do not use.
In simpler terms: Keep your interfaces focused. Each interface should represent a specific capability or behavior. If a class doesn’t need a method, it shouldn’t be forced to implement it.
This is especially important in larger codebases with evolving requirements.
Time to apply ISP and break down our MediaPlayer
interface into more logical, focused pieces.
Instead of one bloated MediaPlayer
interface, we’ll create multiple focused ones:
Now our specific player classes can implement only the relevant interfaces.
ModernAudioPlayer (Audio-only)
SilentVideoPlayer (Video-only)
What if we need a player that handles both? It implements both interfaces!
ComprehensiveMediaPlayer (Both audio + video)
Even with the right intentions, it's easy to misuse ISP if you're not careful. Here are some common traps to avoid:
The mistake: Creating a separate interface for every single method — like Playable
, Stoppable
, AdjustableVolume
, etc.
Why it’s a problem:You end up with too many tiny interfaces that are hard to manage and understand. It’s just as bad as having one big, bloated interface.
What to do instead:Group related methods by logical roles or capabilities.For example:
playAudio()
, stopAudio()
, and adjustAudioVolume()
naturally belong together in an AudioPlayer
interface.The mistake: Designing interfaces based only on how implementers work — not how clients use them.
Why it’s a problem:ISP is really about making life easier for the client — not the implementer.
Fix:Design your interfaces by looking at what the client actually needs to do — and nothing more.
The mistake: Creating interfaces that aren’t tightly related — mixing unrelated methods together.
Why it’s a problem:Low cohesion makes interfaces confusing and hard to reason about.
Fix:Make sure every method in an interface relates to a single, well-defined responsibility.
Think of your interface as a role — would it make sense for all these actions to be part of that role?
There’s no strict number of methods or “one-size-fits-all” guideline. The best rule of thumb is: design interfaces based on client needs.
Ask yourself:
If yes, it’s a strong signal that the interface should be split.
Think in terms of roles or capabilities—interfaces should represent a cohesive set of behaviors that make sense together from the client’s perspective.
At first glance, yes—it might feel like you’re adding more moving parts.
But this is intentional structure, not clutter. Over time, it pays off by:
Instead of trying to comprehend one giant interface with 15 methods, you now deal with clear, focused contracts. It’s a shift from accidental complexity to intentional design.
You should definitely apply ISP when writing new code.
For existing code, refactoring is worth it when you notice any of the following:
UnsupportedOperationException
Start with the interfaces causing the most pain. Focus on the ones that are bloated, unstable, or widely misused.
Absolutely—and that’s one of the key benefits of ISP.
A class can fulfill multiple roles by implementing several small, targeted interfaces. This gives you incredible flexibility and composability.
For example, an AudioPlayer
might implement:
LoadableMedia
PlaybackControls
VolumeControl
AudioFeatures
Each interface is simple and focused, and the class only opts into the behaviors it supports.
ISP and LSP are closely aligned.
When interfaces are too broad (violating ISP), classes are often forced to implement methods they don’t support. This commonly leads to LSP violations like throwing UnsupportedOperationException
where the client expects normal behavior.
By applying ISP, you make LSP easier to follow because each interface becomes a clean, reliable contract that implementers can fulfill completely and correctly.