Interface Segregation Principle (ISP)
"No client should be forced to depend on methods it does not use."
The Interface Segregation Principle states that large interfaces should be split into smaller, more specific ones so that clients only need to know about methods that are relevant to them.
The Problem: Violating ISP
// ❌ BAD: Fat interface forces implementations to include unused methods
interface Worker {
work(): void;
eat(): void;
sleep(): void;
getSalary(): number;
attendMeeting(): void;
code(): void;
}
class Developer implements Worker {
work(): void {
console.log("Writing code");
}
eat(): void {
console.log("Eating lunch");
}
sleep(): void {
console.log("Sleeping");
}
getSalary(): number {
return 80000;
}
attendMeeting(): void {
console.log("Attending standup");
}
code(): void {
console.log("Coding features");
}
}
class Robot implements Worker {
work(): void {
console.log("Assembling parts");
}
// Problem: Robot doesn't eat or sleep!
eat(): void {
throw new Error("Robots don't eat");
}
sleep(): void {
throw new Error("Robots don't sleep");
}
getSalary(): number {
return 0; // Robots don't get paid
}
attendMeeting(): void {
throw new Error("Robots don't attend meetings");
}
code(): void {
throw new Error("Robots don't code");
}
}
// Problems:
// 1. Robot forced to implement methods it doesn't need
// 2. Methods throw errors at runtime instead of compile time
// 3. Interface is too large and inflexibleThe Solution: Applying ISP
// ✅ GOOD: Segregated interfaces
interface Workable {
work(): void;
}
interface Eatable {
eat(): void;
}
interface Sleepable {
sleep(): void;
}
interface Salaried {
getSalary(): number;
}
interface Attendee {
attendMeeting(): void;
}
interface Programmer {
code(): void;
}
// Developer implements only relevant interfaces
class Developer implements Workable, Eatable, Sleepable, Salaried, Attendee, Programmer {
work(): void {
console.log("Working on tasks");
}
eat(): void {
console.log("Eating lunch");
}
sleep(): void {
console.log("Sleeping");
}
getSalary(): number {
return 80000;
}
attendMeeting(): void {
console.log("Attending standup");
}
code(): void {
console.log("Coding features");
}
}
// Robot implements only what it needs
class Robot implements Workable {
work(): void {
console.log("Assembling parts");
}
// No need to implement eat(), sleep(), etc.
}
// Manager implements their relevant interfaces
class Manager implements Workable, Eatable, Sleepable, Salaried, Attendee {
work(): void {
console.log("Managing team");
}
eat(): void {
console.log("Eating lunch");
}
sleep(): void {
console.log("Sleeping");
}
getSalary(): number {
return 100000;
}
attendMeeting(): void {
console.log("Leading meeting");
}
// No code() method - managers don't code
}
// Now we can use specific interfaces
function makeWork(worker: Workable): void {
worker.work();
}
function payEmployee(employee: Salaried): void {
console.log(`Paying $${employee.getSalary()}`);
}
function scheduleLunch(person: Eatable): void {
person.eat();
}
makeWork(new Developer()); // ✓ Works
makeWork(new Robot()); // ✓ Works
makeWork(new Manager()); // ✓ Works
payEmployee(new Developer()); // ✓ Works
// payEmployee(new Robot()); // ✓ Compile error - Robot doesn't implement Salaried
scheduleLunch(new Developer()); // ✓ Works
// scheduleLunch(new Robot()); // ✓ Compile error - Robot doesn't implement EatableReal-World Example: Printer Interfaces
// ❌ BAD: One large interface
interface Printer {
print(document: string): void;
scan(document: string): string;
fax(document: string): void;
staple(): void;
}
class AllInOnePrinter implements Printer {
print(document: string): void {
console.log(`Printing: ${document}`);
}
scan(document: string): string {
return `Scanned: ${document}`;
}
fax(document: string): void {
console.log(`Faxing: ${document}`);
}
staple(): void {
console.log("Stapling documents");
}
}
class SimplePrinter implements Printer {
print(document: string): void {
console.log(`Printing: ${document}`);
}
// Problem: Simple printer can't do these!
scan(document: string): string {
throw new Error("This printer cannot scan");
}
fax(document: string): void {
throw new Error("This printer cannot fax");
}
staple(): void {
throw new Error("This printer cannot staple");
}
}
// ✅ GOOD: Segregated printer interfaces
interface Printable {
print(document: string): void;
}
interface Scannable {
scan(document: string): string;
}
interface Faxable {
fax(document: string): void;
}
interface Stapleable {
staple(): void;
}
class SimplePrinter implements Printable {
print(document: string): void {
console.log(`Printing: ${document}`);
}
}
class MultiFunctionPrinter implements Printable, Scannable {
print(document: string): void {
console.log(`Printing: ${document}`);
}
scan(document: string): string {
return `Scanned: ${document}`;
}
}
class AllInOnePrinter implements Printable, Scannable, Faxable, Stapleable {
print(document: string): void {
console.log(`Printing: ${document}`);
}
scan(document: string): string {
return `Scanned: ${document}`;
}
fax(document: string): void {
console.log(`Faxing: ${document}`);
}
staple(): void {
console.log("Stapling documents");
}
}
// Use specific capabilities
function printDocument(printer: Printable, doc: string): void {
printer.print(doc);
}
function scanDocument(scanner: Scannable, doc: string): string {
return scanner.scan(doc);
}
printDocument(new SimplePrinter(), "Report"); // ✓ Works
printDocument(new MultiFunctionPrinter(), "Report"); // ✓ Works
printDocument(new AllInOnePrinter(), "Report"); // ✓ Works
// scanDocument(new SimplePrinter(), "Doc"); // ✓ Compile error
scanDocument(new MultiFunctionPrinter(), "Doc"); // ✓ Works
scanDocument(new AllInOnePrinter(), "Doc"); // ✓ WorksDatabase Connection Example
// ❌ BAD: Monolithic interface
interface Database {
connect(): void;
disconnect(): void;
query(sql: string): any[];
transaction(callback: () => void): void;
backup(): void;
restore(backupFile: string): void;
optimize(): void;
analyze(): void;
}
// ✅ GOOD: Segregated interfaces
interface Connectable {
connect(): void;
disconnect(): void;
}
interface Queryable {
query(sql: string): any[];
}
interface Transactional {
beginTransaction(): void;
commit(): void;
rollback(): void;
}
interface Backupable {
backup(): void;
restore(backupFile: string): void;
}
interface Optimizable {
optimize(): void;
analyze(): void;
}
// Simple database connection
class BasicDatabaseConnection implements Connectable, Queryable {
connect(): void {
console.log("Connected to database");
}
disconnect(): void {
console.log("Disconnected from database");
}
query(sql: string): any[] {
console.log(`Executing: ${sql}`);
return [];
}
}
// Advanced database connection
class AdvancedDatabaseConnection
implements Connectable, Queryable, Transactional, Backupable, Optimizable {
connect(): void {
console.log("Connected");
}
disconnect(): void {
console.log("Disconnected");
}
query(sql: string): any[] {
return [];
}
beginTransaction(): void {
console.log("Transaction started");
}
commit(): void {
console.log("Transaction committed");
}
rollback(): void {
console.log("Transaction rolled back");
}
backup(): void {
console.log("Backup created");
}
restore(backupFile: string): void {
console.log(`Restored from ${backupFile}`);
}
optimize(): void {
console.log("Database optimized");
}
analyze(): void {
console.log("Database analyzed");
}
}
// Read-only connection
class ReadOnlyConnection implements Connectable, Queryable {
connect(): void {
console.log("Connected (read-only)");
}
disconnect(): void {
console.log("Disconnected");
}
query(sql: string): any[] {
if (sql.toUpperCase().includes("INSERT") ||
sql.toUpperCase().includes("UPDATE") ||
sql.toUpperCase().includes("DELETE")) {
throw new Error("Write operations not allowed");
}
return [];
}
}Benefits of ISP
Better flexibility: Classes implement only what they need
Easier maintenance: Changes to one interface don't affect unrelated classes
Better code organization: Clear separation of concerns
Compile-time safety: Errors caught at compile time, not runtime
Keep your own version of these notes — editable, searchable, and organised by your stack.
Start free