ISP (Interface Segregation Principle) is the fourth SOLID principle, which states: clients should not depend on methods they do not use. The principle was formulated by Robert Martin in the context of interface design for object-oriented systems. As described in the book Clean Architecture (2017), the interface segregation principle requires creating narrowly specialized interfaces instead of a single universal one, which reduces coupling and simplifies making changes.
Key Takeaways
ISP (Interface Segregation Principle) is the principle of interface segregation that prohibits creating “fat” interfaces with methods not used by all clients. Instead of one interface with a dozen methods, several small interfaces are designed, each for its own group of clients.
The principle was introduced by Robert Martin as a solution to the problem of “interface pollution,” when a class is forced to implement methods it does not need simply because they are declared in a common interface. In statically typed languages, this results in empty implementations or exception throws — a direct sign of ISP violation.
ISP and SRP complement each other: SRP is about class responsibility, ISP is about interface contracts. SRP says “one class — one reason to change,” ISP says “one interface — one client scenario.” Together they form a modular architecture where each element of the system has clear boundaries.
Fat Interface — an interface containing more methods than a specific client needs. For example, a Worker interface with work, eat, sleep methods. A robot worker should not implement eat and sleep, but is forced to. The solution is to split into Workable, Eatable, Sleepable. Each client gets exactly what it needs.
In mobile development, fat interfaces are found in delegate protocols and DataSource. One protocol can contain methods for two different scenarios (editing + display), even though a specific screen uses only one of them.
Implementing ISP starts with analyzing the clients of each interface. If two clients use different sets of methods of one interface — the interface should be split. Each new interface groups methods that are called together within a single scenario.
The splitting mechanism: the original interface is broken into several narrow ones, each inheriting the common part (if any). Clients switch to depending on the required narrow interface instead of the general one. Classes implementing the original interface now implement only those narrow interfaces that they actually need.
An important clarification: the degree of splitting is determined by the number of clients and their scenarios. ISP does not require maximum splitting (micro-interfaces with one method each). This would lead to excessive complexity. The goal is to eliminate client dependency on unnecessary methods, not to minimize the size of each interface.
Main signs of ISP violation include: classes implementing an interface with empty methods (dummy implementation), throwing UnsupportedOperationException in implementations, a large number of parameters or return types not used by some clients, and frequent interface changes affecting only some clients.
In Android development, a typical example of ISP violation is the OnItemClickListener interface, which includes methods for click, long click, and swipe. If a specific screen only uses click — the remaining methods stay empty. The solution is to split into OnItemClickListener, OnItemLongClickListener, OnItemSwipeListener.
In iOS development, ISP violation manifests in UIKit delegates: one protocol contains methods for different component states. UITableViewDelegate includes methods for display, selection, editing, and swipe-action. Developers often implement the entire protocol with a dozen empty methods. Splitting into several protocols by responsibility groups solves the problem.
The problem is not just about code aesthetics. When an interface changes (a new method is added), all implementing classes must be updated — even those that do not need the new method. In mobile development with dozens of screens, this leads to cascading changes. ISP isolates each client from changes that do not concern it.
Implicit ISP violation occurs through configuration parameters. If a method accepts an object with many fields, and the client uses only 2-3 of them — this is a signal to split. Alternative: several specialized methods with a minimal set of parameters.
In Android development, ISP is violated when using a single SharedPreferencesManager for reading and writing all application settings. A Fragment that only needs to read the theme gets a dependency on a global manager with a dozen methods for different data types. Splitting into ThemePreferenceProvider, AuthPreferenceProvider, FeatureFlagProvider — applying ISP at the configuration service level. Each provider contains exactly the methods its clients need.
Let’s consider an Android example with an interface for working with data. ISP violation — one interface for all CRUD operations, even though not all clients need all operations.
// ISP Violation: Fat Interface
interface UserRepository {
fun getAll(): List<User>
fun getById(id: Int): User
fun save(user: User)
fun delete(id: Int)
}
// After Applying ISP: Narrow Interfaces
interface UserReader {
fun getAll(): List<User>
fun getById(id: Int): User
}
interface UserWriter {
fun save(user: User)
fun delete(id: Int)
}
// ReadOnlyViewModel does not depend on write methods
class ReadOnlyViewModel(
private val reader: UserReader
)
An iOS example with protocol separation for working with media:
// ISP Violation: One Protocol for All Media Work
protocol MediaService {
func play(url: URL)
func pause()
func stop()
func upload(data: Data) async -> URL
func download(url: URL) async -> Data
}
// After ISP: Separation into Protocols by Responsibility
protocol MediaPlayer {
func play(url: URL)
func pause()
func stop()
}
protocol MediaTransfer {
func upload(data: Data) async -> URL
func download(url: URL) async -> Data
}
// PlayerViewModel does not depend on download methods
class PlayerViewModel {
private let player: MediaPlayer
}
Practical conclusion: ISP protects clients from changes in unrelated parts of the interface. Splitting UserRepository into UserReader and UserWriter means that changes in save do not affect ReadOnlyViewModel, and vice versa. Each client is isolated from functionality it does not use and does not require changes when other parts of the system are modified.
ISP and SRP — a natural pair. SRP defines that a class should have one reason to change. ISP applies the same logic to interfaces: an interface should serve one client scenario. A class can implement several narrow interfaces (each corresponding to one responsibility), which is cleaner than one fat interface with multiple responsibilities.
ISP and OCP are also related: narrow interfaces are easier to extend. Adding a new method to a narrow interface only affects its clients. Adding a method to a fat interface affects all clients — potentially violating OCP if clients are forced to change their implementation.
ISP and DIP work together: DIP requires dependency on abstractions. ISP makes these abstractions narrow and focused. Depending on a wide interface is still a dependency on an abstraction, but a “bad” abstraction from the ISP perspective. Four principles (SRP, OCP, ISP, DIP) form the “modularity pyramid”: SRP and ISP define boundaries, OCP and DIP define ways of extension and coupling.
Component architecture in mobile projects (modules, features, layers) benefits from ISP at the public API level. Each module exports narrow interfaces for its consumers, rather than a single common facade. This allows changing the module’s internal implementation without affecting consumers that use only part of its functionality.
In Android projects with Clean Architecture, ISP is applied to UseCases: each UseCase is a separate interface with a single invoke or execute method. The client (ViewModel) depends only on the UseCase it needs, rather than on an entire repository. This makes dependencies transparent and testable.
Frequently Asked Questions
Yes, excessive splitting is possible. ISP does not require one interface per method. The criterion is: is there a client that needs only part of the interface methods? If all clients use all methods — the interface does not need to be split. The optimal level of splitting is determined by real usage scenarios.
ISP at the parameter level means: a function should not accept objects with a large number of fields if it only uses part of them. Instead, you should pass only the necessary data or use specialized interfaces (for example, a Renderable interface instead of a full User).
LSP is about correct inheritance and behavioral subtype compatibility. ISP is about interface design: clients should not depend on methods they do not use. LSP answers the question “can a subclass be used instead of a base class?”, ISP answers “does the client need the entire interface?”
Narrow interfaces simplify creating mock objects: the test creates a mock with one or two methods, not a dozen. The fewer methods in an interface, the easier it is to stub its behavior. This reduces cognitive load on the test developer and decreases the likelihood of errors in mock logic.
If the interface is stable and all clients use all methods — splitting is redundant. A typical example: UIKit protocols designed by Apple. Splitting them is risky because UIKit expects full delegate implementation. In such cases, ISP violation is justified by API stability.
Summary
We will develop a mobile application turnkey
IT Sectr creates iOS and Android applications for startups and businesses since 2017. We will advise you and propose the best solution.
Read also