SOLID Principles
The five foundational principles of object-oriented design that lead to maintainable, scalable software.
What is SOLID?
SOLID is a mnemonic for five design principles that help create maintainable, flexible, and understandable object-oriented code.
| Principle | Full Name |
|---|---|
| S | Single Responsibility Principle |
| O | Open/Closed Principle |
| L | Liskov Substitution Principle |
| I | Interface Segregation Principle |
| D | Dependency Inversion Principle |
S — Single Responsibility Principle
Definition
A class should have one and only one reason to change.
// ❌ Bad: Multiple responsibilities
class UserService {
createUser(data: UserData) { /* ... */ }
sendEmail(user: User, template: string) { /* ... */ }
generateReport(users: User[]) { /* ... */ }
}
// ✅ Good: Single responsibility each
class UserService {
createUser(data: UserData) { /* ... */ }
}
class EmailService {
sendEmail(to: string, template: string) { /* ... */ }
}
class ReportService {
generateReport(users: User[]) { /* ... */ }
}
O — Open/Closed Principle
Definition
Software entities should be open for extension but closed for modification.
// ❌ Bad: Must modify class for each new shape
class AreaCalculator {
calculate(shape: any): number {
if (shape.type === 'circle') return Math.PI * shape.radius ** 2;
if (shape.type === 'rectangle') return shape.width * shape.height;
// Must add new if-else for each shape...
}
}
// ✅ Good: Extend without modifying
interface Shape {
area(): number;
}
class Circle implements Shape {
constructor(private radius: number) {}
area(): number { return Math.PI * this.radius ** 2; }
}
class Rectangle implements Shape {
constructor(private width: number, private height: number) {}
area(): number { return this.width * this.height; }
}
L — Liskov Substitution Principle
Definition
Subtypes must be substitutable for their base types without altering the correctness of the program.
I — Interface Segregation Principle
Definition
Clients should not be forced to depend on interfaces they do not use.
// ❌ Bad: Fat interface
interface Worker {
work(): void;
eat(): void;
sleep(): void;
}
// A robot can't eat or sleep!
class Robot implements Worker {
work() { /* ... */ }
eat() { throw new Error("Robots don't eat"); } // Forced!
sleep() { throw new Error("Robots don't sleep"); }
}
// ✅ Good: Segregated interfaces
interface Workable { work(): void; }
interface Eatable { eat(): void; }
interface Sleepable { sleep(): void; }
class Robot implements Workable {
work() { /* ... */ }
}
class Human implements Workable, Eatable, Sleepable {
work() { /* ... */ }
eat() { /* ... */ }
sleep() { /* ... */ }
}
D — Dependency Inversion Principle
Definition
High-level modules should not depend on low-level modules. Both should depend on abstractions.
// ❌ Bad: High-level depends on low-level
class OrderService {
private mysqlDb = new MySQLDatabase();
saveOrder(order: Order) {
this.mysqlDb.insert('orders', order); // Tightly coupled!
}
}
// ✅ Good: Both depend on abstraction
interface Database {
insert(table: string, data: any): void;
}
class OrderService {
constructor(private db: Database) {} // Injected!
saveOrder(order: Order) {
this.db.insert('orders', order);
}
}
Interview Application
SOLID principles are foundational for LLD interviews. When designing a system, explicitly mention which principles guide your class structure.