Bird
Raised Fist0
LLDsystem_design~7 mins

Applying SOLID to real code in LLD - System Design Guide

Choose your learning style10 modes available
Problem Statement
When code is tightly coupled and poorly organized, adding new features or fixing bugs becomes risky and slow. Changes in one part can break others unexpectedly, making the system fragile and hard to maintain.
Solution
Applying SOLID principles breaks code into smaller, focused parts with clear responsibilities. This reduces dependencies and makes the code easier to extend, test, and maintain without unintended side effects.
Architecture
┌───────────────┐       ┌───────────────┐       ┌───────────────┐
│  Single Class │──────▶│  Multiple     │──────▶│  Small Classes │
│  Doing Many   │       │  Classes with │       │  with One      │
│  Jobs         │       │  Clear Roles  │       │  Responsibility│
└───────────────┘       └───────────────┘       └───────────────┘

This diagram shows the evolution from one big class doing many jobs to multiple small classes each with a single responsibility, illustrating the Single Responsibility Principle from SOLID.

Trade-offs
✓ Pros
→
Improves code readability by separating concerns clearly.
→
Makes adding new features safer and easier without breaking existing code.
→
Facilitates unit testing by isolating functionality.
→
Enhances maintainability and reduces bugs caused by tight coupling.
✗ Cons
→
Initial design takes more time and thought to apply SOLID correctly.
→
May increase the number of classes and files, which can feel complex at first.
→
Over-applying SOLID can lead to unnecessary abstraction and complexity.
Use SOLID principles when building or refactoring medium to large codebases where maintainability, extensibility, and testability are priorities.
Avoid strict SOLID application in very small or throwaway scripts where simplicity and speed are more important than long-term maintenance.
Real World Examples
Amazon
Amazon applies SOLID principles in their microservices to ensure each service has a single responsibility, making it easier to update and deploy independently.
Netflix
Netflix uses SOLID principles in their backend code to keep services modular and maintainable, enabling rapid feature development without breaking existing functionality.
Uber
Uber applies SOLID to their ride-matching algorithms to separate concerns like pricing, routing, and driver availability, improving code clarity and scalability.
Code Example
The before code mixes calculation, saving, and emailing in one class, violating Single Responsibility Principle. The after code separates these concerns into distinct classes, making it easier to extend or modify each part independently, following SOLID principles.
LLD
### Before applying SOLID (violating SRP and OCP):

class OrderProcessor:
    def __init__(self, order):
        self.order = order

    def calculate_total(self):
        total = 0
        for item in self.order.items:
            total += item.price * item.quantity
        return total

    def save_order(self):
        # code to save order to database
        pass

    def send_confirmation_email(self):
        # code to send email
        pass


### After applying SOLID (SRP and OCP applied):

class OrderCalculator:
    def calculate_total(self, order):
        total = 0
        for item in order.items:
            total += item.price * item.quantity
        return total

class OrderRepository:
    def save(self, order):
        # code to save order to database
        pass

class EmailService:
    def send_confirmation(self, order):
        # code to send email
        pass

class OrderProcessor:
    def __init__(self, calculator, repository, email_service):
        self.calculator = calculator
        self.repository = repository
        self.email_service = email_service

    def process(self, order):
        total = self.calculator.calculate_total(order)
        self.repository.save(order)
        self.email_service.send_confirmation(order)
        return total
OutputSuccess
Alternatives
Procedural Programming
Focuses on sequences of instructions and shared state rather than objects with responsibilities.
Use when: Use for simple scripts or programs with straightforward logic and minimal complexity.
Anemic Domain Model
Separates data and behavior, often leading to procedural style within object-oriented code.
Use when: Choose when domain logic is simple and business rules are minimal, but avoid for complex domains.
Summary
Applying SOLID principles prevents fragile, tightly coupled code by enforcing clear responsibilities and modular design.
This approach improves maintainability, extensibility, and testability of software systems.
Balancing SOLID with simplicity is important to avoid unnecessary complexity.

Practice

(1/5)
1. Which SOLID principle encourages a class to have only one reason to change?
easy
A. Dependency Inversion Principle
B. Open/Closed Principle
C. Liskov Substitution Principle
D. Single Responsibility Principle

Solution

  1. Step 1: Understand the principle names

    Single Responsibility Principle means one class should do one job only.
  2. Step 2: Match principle to description

    Only Single Responsibility Principle talks about one reason to change.
  3. Final Answer:

    Single Responsibility Principle -> Option D
  4. Quick Check:

    One class, one reason to change = Single Responsibility Principle [OK]
Hint: One class, one job means Single Responsibility Principle [OK]
Common Mistakes:
  • Confusing Single Responsibility with Open/Closed
  • Thinking Dependency Inversion means single responsibility
  • Mixing Liskov Substitution with class responsibilities
2. Which of the following code snippets correctly applies the Dependency Inversion Principle?
easy
A. class Car { Engine engine = new Engine(); }
B. interface Engine { void start(); } class Car { Engine engine; Car(Engine e) { engine = e; } }
C. class Car { void start() { Engine engine = new Engine(); engine.start(); } }
D. class Engine { void start() {} } class Car extends Engine {}

Solution

  1. Step 1: Identify Dependency Inversion Principle

    It says depend on abstractions (interfaces), not concrete classes.
  2. Step 2: Check which code uses interfaces

    interface Engine { void start(); } class Car { Engine engine; Car(Engine e) { engine = e; } } uses interface Engine and injects it into Car, following DIP.
  3. Final Answer:

    Interface with injection to depend on abstraction -> Option B
  4. Quick Check:

    Depend on interface, not concrete class = DIP [OK]
Hint: Look for interface usage and constructor injection [OK]
Common Mistakes:
  • Creating concrete objects inside classes (violates DIP)
  • Extending concrete classes instead of using interfaces
  • Not using constructor or setter injection
3. Given this code snippet, what will be the output?
interface Shape { double area(); }
class Rectangle implements Shape {
  double width, height;
  Rectangle(double w, double h) { width = w; height = h; }
  public double area() { return width * height; }
}
class Circle implements Shape {
  double radius;
  Circle(double r) { radius = r; }
  public double area() { return 3.14 * radius * radius; }
}
class AreaCalculator {
  double totalArea(Shape[] shapes) {
    double total = 0;
    for (Shape s : shapes) total += s.area();
    return total;
  }
}
public class Main {
  public static void main(String[] args) {
    Shape[] shapes = { new Rectangle(2,3), new Circle(1) };
    System.out.println(new AreaCalculator().totalArea(shapes));
  }
}
medium
A. 9.14
B. 12.14
C. 15.42
D. Error at runtime

Solution

  1. Step 1: Calculate Rectangle area

    Rectangle area = 2 * 3 = 6
  2. Step 2: Calculate Circle area

    Circle area = 3.14 * 1 * 1 = 3.14
  3. Step 3: Sum areas

    Total area = 6 + 3.14 = 9.14
  4. Final Answer:

    9.14 -> Option A
  5. Quick Check:

    6 + 3.14 = 9.14 [OK]
Hint: Add areas of shapes using interface method calls [OK]
Common Mistakes:
  • Using wrong formula for circle area
  • Adding radius instead of area
  • Forgetting to sum both shapes
4. Identify the violation of the Open/Closed Principle in this code:
class Report {
  void printReport(String type) {
    if (type.equals("PDF")) { System.out.println("Printing PDF"); }
    else if (type.equals("HTML")) { System.out.println("Printing HTML"); }
  }
}
medium
A. Adding new report types requires changing printReport method
B. Using if-else is always correct for Open/Closed Principle
C. The class is closed for modification
D. The method uses polymorphism correctly

Solution

  1. Step 1: Understand Open/Closed Principle

    Classes should be open for extension but closed for modification.
  2. Step 2: Analyze printReport method

    Adding new types means editing if-else, so modifying existing code.
  3. Final Answer:

    Adding new report types requires changing printReport method -> Option A
  4. Quick Check:

    Modifying code to add features breaks Open/Closed Principle [OK]
Hint: If adding features needs code change, Open/Closed is violated [OK]
Common Mistakes:
  • Thinking if-else is always fine
  • Confusing Open/Closed with Single Responsibility
  • Ignoring need for polymorphism
5. You have a payment system with classes for CreditCardPayment and PayPalPayment. To follow the Interface Segregation Principle, how should you design the interfaces?
hard
A. Create one large interface Payment with all methods for all payment types
B. Make CreditCardPayment inherit from PayPalPayment
C. Create separate interfaces like CreditCardPaymentInterface and PayPalPaymentInterface with only relevant methods
D. Use a single abstract class with all payment methods implemented

Solution

  1. Step 1: Understand Interface Segregation Principle

    Clients should not depend on methods they do not use; interfaces should be small and specific.
  2. Step 2: Apply to payment system

    Separate interfaces for each payment type avoid forcing classes to implement unused methods.
  3. Final Answer:

    Create separate interfaces like CreditCardPaymentInterface and PayPalPaymentInterface with only relevant methods -> Option C
  4. Quick Check:

    Small, specific interfaces = Interface Segregation Principle [OK]
Hint: Split interfaces by client needs to avoid unused methods [OK]
Common Mistakes:
  • Using one big interface for all payment types
  • Inheriting unrelated payment classes
  • Using abstract class forcing all methods
Recommended

Ready to prove it?

Pass the test and this pattern moves unknown strong while earning +1 Grip toward your certificate.

10 questions~5 mineasy hard