Last Updated: February 13, 2026
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 watched multiple unrelated classes start breaking?
If so, you have probably run into a violation of one of the most misunderstood design principles in software engineering: the Interface Segregation Principle (ISP).
This chapter explains what ISP really means, why fat interfaces cause problems, how to break them apart, and how to avoid common mistakes when applying the principle. Let's start with a real-world example.
Imagine you are 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, this 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:
Even though AudioOnlyPlayer only needs audio methods, it is forced to implement unrelated video functionality. You either throw exceptions or write empty methods. Neither is a good outcome.
The MediaPlayer interface is doing too much. It combines multiple unrelated responsibilities: audio playback, video playback, subtitle handling, and brightness control.
Any class that implements this interface must carry the weight of all seven methods, even when it only needs three. This is what's known as a "fat" or "polluted" interface.
Now, imagine you add a new method to the interface, like enablePictureInPicture(). Suddenly, all existing implementations, audio-only, video-only, or otherwise, must be updated.
This tight coupling slows you down and increases the risk of bugs. One method addition forces changes across files that have nothing to do with picture-in-picture mode.
A client may expect any MediaPlayer to support video, but passing in an AudioOnlyPlayer will crash the program with an UnsupportedOperationException.
That is a clear Liskov Substitution Principle (LSP) violation. If a subtype cannot stand in for its base type without breaking callers, the contract is unreliable.
Half of AudioOnlyPlayer's methods are either no-ops or throw exceptions. That is a clear sign the interface is too broad.
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. The more methods an interface has, the more likely it is that a change to one method will ripple out into classes that have nothing to do with the change.
ISP was coined by Robert C. Martin (Uncle Bob) and is the fourth principle in the SOLID acronym. It applies to any language that supports interfaces or abstract base classes, but it is particularly relevant in statically typed languages where the compiler enforces that every method in an interface is implemented.
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. No more empty methods, no more exceptions for unsupported operations.
What if you need a player that handles both audio and video? It simply implements both interfaces.
Now each class implements only the interfaces it needs. ModernAudioPlayer handles audio, SilentVideoPlayer handles video, and ComprehensiveMediaPlayer opts into both. No empty methods, no exceptions, no wasted code.
This is the essence of the Interface Segregation Principle in action. The interfaces are small, focused, and composable. A class picks up only the contracts it can fully honor.
Even with the right intentions, it is easy to misuse ISP if you are not careful. Here are three common traps to avoid.
The mistake is creating a separate interface for every single method, like Playable, Stoppable, AdjustableVolume, and so on.
This is a problem because you end up with too many tiny interfaces that are hard to manage and understand. It is just as bad as having one big, bloated interface, only now the complexity is spread across dozens of files instead of concentrated in one.
Instead, group related methods by logical roles or capabilities. For example, playAudio(), stopAudio(), and adjustAudioVolume() naturally belong together in an AudioPlayerControls interface. They represent one cohesive role: controlling audio playback.
The mistake is designing interfaces based only on how implementers work, not how clients use them.
ISP is really about making life easier for the client, not the implementer. If a client only needs to control audio playback, it should depend on an AudioPlayerControls interface, not a giant MediaPlayer that exposes video methods the client will never call.
Design your interfaces by looking at what the client actually needs to do, and nothing more. Start with the client code and ask: "What is the minimal set of methods this caller requires?"
The mistake is creating interfaces that are not tightly related, mixing unrelated methods together.
Low cohesion makes interfaces confusing and hard to reason about. If an interface has methods that don't logically belong together, you have the same problem as a fat interface, just with a different name.
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:
UnsupportedOperationExceptionStart 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:
LoadableMediaPlaybackControlsVolumeControlAudioFeaturesEach 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.