random content
In object-oriented programming, there is a principle with a rather long name: the Interface Segregation Principle (ISP).
It may sound academic, but the idea is very simple:
“Don’t force others to depend on things they don’t need.”
The Interface Segregation Principle (ISP) is the I in the SOLID principles. It states that:
Clients (classes) should not be forced to depend on methods they do not use.
In other words, instead of having one huge interface that contains all kinds of functionality, we should split it into smaller, more focused interfaces that match real needs.
Imagine you hire a handyman to fix things in your house.
You only want them to change a light bulb and fix a water tap.
But the contract they give you is a full A4 page long: it includes repairing elevators, installing cameras, setting up Wi-Fi, and even… training dogs 🐶.
How would you feel? Probably amused and annoyed at the same time.
In code, an interface that is too large creates exactly the same feeling.
Suppose we are building an office printer system.
You define an “all-in-one” interface like this:
interface Machine {
print(content: string): void;
scan(): string;
fax(content: string): void;
}
It sounds reasonable… until you have an old printer that can only print—no scanning, no faxing.
class OldPrinter implements Machine {
print(content: string) {
console.log("Printing:", content);
}
scan(): string {
throw new Error("Not supported");
}
fax(content: string): void {
throw new Error("Not supported");
}
}
👉 Clearly, OldPrinter does not want (and cannot) implement scan or fax.
But because the Machine interface forces it, it has to declare them anyway.
This is a code smell — a sign that the design is not quite right.
Instead of one giant interface, we split it into smaller, separate interfaces:
interface Printer {
print(content: string): void;
}
interface Scanner {
scan(): string;
}
interface Fax {
fax(content: string): void;
}
Now each class only implements what it actually needs:
class SimplePrinter implements Printer {
print(content: string) {
console.log("Printing:", content);
}
}
class MultiFunctionPrinter implements Printer, Scanner, Fax {
print(content: string) {
console.log("Printing:", content);
}
scan(): string {
return "Scanned content";
}
fax(content: string) {
console.log("Faxing:", content);
}
}
SimplePrinter only prints.
MultiFunctionPrinter supports all three features.
No more forced implementations. The code becomes clearer and easier to maintain.
In TypeScript and NestJS—tools commonly used by backend developers—ISP is very practical.
With NestJS, you can create many small services, each implementing a specific interface. For example:
EmailService only handles sending emails.
SmsService only handles SMS.
PushNotificationService handles app notifications.
A notification system can rely on a common Notifier interface, while each notifier implements only what it needs.
Thanks to this, when you test or change an implementation (for example, switching from Twilio to AWS SNS), you don’t have to touch a bunch of unrelated methods.
Don’t split interfaces too aggressively. If a group of methods always goes together, they probably belong in the same interface.
ISP is especially important in large systems with many interacting modules. In small projects, apply it thoughtfully to avoid unnecessary complexity.
Don’t create overly complicated “contracts.” Split responsibilities so each component only does its own job.
In code, this means interfaces should be small, clear, and free of unnecessary requirements.
In real life, it’s like hiring the right person for the right job—you don’t need a “jack of all trades” just to change a light bulb 😄.