Phase 2: SOLID Principles

Non-negotiable for SDE-2. SRP, OCP, LSP, ISP, and DIP. Understand what problem each principle solves to write maintainable code.

Every single scalable architecture relies on these 5 principles. Interviewers will actively look for violations of SOLID in your machine coding rounds.

1. SRP (Single Responsibility Principle)

A class should have one, and ONLY one, reason to change. God classes that mix business logic, database saving, and UI formatting are immediate red flags.
typescript
// ❌ BAD: God Class (Multiple reasons to change)
class EmployeeManager {
    calculatePay(empId: string): number { /* logic here */ return 0; }
    saveToDatabase(emp: any): void { /* DB logic here */ }
    generateReportForm(empId: string): string { /* UI/Format logic here */ return ""; }
}

// ✅ GOOD: Split into distinct responsibilities
class PayCalculator { calculatePay(empId: string): number { return 0; } }
class EmployeeRepository { saveToDatabase(emp: any): void {} }
class EmployeeReportGenerator { generateReportForm(empId: string): string { return ""; } }

2. OCP (Open-Closed Principle)

Software entities should be **Open for Extension, but Closed for Modification**. If you need to add a new feature, you write NEW code instead of changing OLD code.
typescript
// ❌ BAD: You must modify this class to add 'Crypto'
class PaymentProcessor {
    processPayment(type: string, amount: number) {
        if (type === "CreditCard") { /* ... */ }
        else if (type === "PayPal") { /* ... */ }
    }
}

// ✅ GOOD: Use an Interface (Strategy Pattern)
interface IPaymentStrategy { process(amount: number): boolean; }

class CreditCardStrategy implements IPaymentStrategy { process(amount: number) { return true; } }
class PayPalStrategy implements IPaymentStrategy { process(amount: number) { return true; } }
class CryptoStrategy implements IPaymentStrategy { process(amount: number) { return true; } } // Added without touching old code!

class PaymentManager {
    processPayment(strategy: IPaymentStrategy, amount: number) {
        strategy.process(amount);
    }
}

3. LSP (Liskov Substitution Principle)

A subclass should be replaceable by its parent class without breaking the application. Don't force behaviors that aren't true.
typescript
// ❌ BAD: Ostrich is a Bird, but throws an exception on fly()
class Bird { fly(): void {} }
class Ostrich extends Bird {
    fly(): void { throw new Error("I can't fly!"); } 
    // ^ This breaks code that expects all Birds to fly safely
}

// ✅ GOOD: Reconstruct the hierarchy based on capabilities
class Bird {} 
class FlyingBird extends Bird { fly(): void {} }
class FlightlessBird extends Bird { run(): void {} }

class Sparrow extends FlyingBird {}
class Ostrich extends FlightlessBird {}

4. ISP (Interface Segregation Principle)

Clients shouldn't be forced to depend on interfaces they do not use. Break fat interfaces into smaller, more granular ones.
typescript
// ❌ BAD: Fat Interface
interface IWorker {
    work(): void;
    eat(): void; // Robots don't eat!
}

// ✅ GOOD: Segregated Interfaces
interface IWorkable { work(): void; }
interface IFeedable { eat(): void; }

class HumanWorker implements IWorkable, IFeedable {
    work() {} eat() {}
}
class RobotWorker implements IWorkable {
    work() {} // No longer forced to implement eat()
}

5. DIP (Dependency Inversion Principle)

High-level modules should not depend on low-level modules. Both should depend on abstractions.
typescript
// ❌ BAD: High-level NotificationService depends tightly on EmailService
class EmailService { send(msg: string) {} }
class NotificationService {
    private emailService = new EmailService(); // Tightly coupled!
    notify(msg: string) { this.emailService.send(msg); }
}

// ✅ GOOD: Both depend on the IMessageSender abstraction
interface IMessageSender { send(msg: string): void; }

class EmailService implements IMessageSender { send(msg: string) {} }
class SMSService implements IMessageSender { send(msg: string) {} }

class NotificationService {
    private sender: IMessageSender;
    
    // Injected dependency! We can pass SMS, Email, or Mock.
    constructor(sender: IMessageSender) { this.sender = sender; }
    notify(msg: string) { this.sender.send(msg); }
}

🎯

Take the Phase Interview

Solidify your knowledge. We will ask you 5 random highly-technical questions from the Phase 2: SOLID Principles bucket. A Staff Engineer AI will strictly evaluate your answers.