Bird
Raised Fist0
LLDsystem_design~25 mins

Applying SOLID to real code in LLD - System Design Exercise

Choose your learning style10 modes available
Design: Modular Online Bookstore
Focus on backend code design applying SOLID principles. UI and database implementation details are out of scope.
Functional Requirements
FR1: Users can browse books by category and search by title or author
FR2: Users can add books to a shopping cart and place orders
FR3: Admin can add, update, or remove books
FR4: System should allow easy addition of new payment methods without changing existing code
FR5: System should support multiple types of notifications (email, SMS) for order status updates
Non-Functional Requirements
NFR1: Codebase must be easy to maintain and extend
NFR2: Changes to one feature should not affect unrelated features
NFR3: System should handle up to 1000 concurrent users
NFR4: Response time for browsing and searching should be under 300ms
Think Before You Design
Questions to Ask
❓ Question 1
❓ Question 2
❓ Question 3
❓ Question 4
❓ Question 5
Key Components
Book catalog service
Shopping cart service
Order processing service
Payment processing interface and implementations
Notification service interface and implementations
Design Patterns
Interface segregation
Dependency injection
Strategy pattern for payment methods
Observer pattern for notifications
Single responsibility principle in service classes
Reference Architecture
  +-------------------+       +---------------------+       +---------------------+
  |   Book Catalog    |<----->|  Shopping Cart       |<----->|  Order Processing    |
  |   Service         |       |  Service            |       |  Service            |
  +-------------------+       +---------------------+       +---------------------+
           ^                             ^                             ^
           |                             |                             |
           |                             |                             |
  +-------------------+       +---------------------+       +---------------------+
  | Payment Interface  |<----->| Payment Implementations|     | Notification Interface|
  +-------------------+       +---------------------+       +---------------------+
                                                                ^           ^
                                                                |           |
                                                      +----------------+ +----------------+
                                                      | EmailNotifier  | | SMSNotifier    |
                                                      +----------------+ +----------------+
Components
Book Catalog Service
Plain classes with single responsibility
Manage book data and provide search and browse functionality
Shopping Cart Service
Class managing cart items and user sessions
Handle adding/removing books and cart state
Order Processing Service
Class coordinating order placement and payment
Process orders and trigger notifications
Payment Interface
Interface with multiple implementations (e.g., CreditCardPayment, PayPalPayment)
Define payment method contract to allow extension without modification
Notification Interface
Interface with implementations like EmailNotifier and SMSNotifier
Send order status updates using different channels
Request Flow
1. User requests book list from Book Catalog Service
2. Book Catalog Service returns filtered book data
3. User adds books to Shopping Cart Service
4. User places order via Order Processing Service
5. Order Processing Service calls Payment Interface implementation to process payment
6. On payment success, Order Processing Service triggers Notification Interface implementations
7. Notifications sent to user via email and/or SMS
Database Schema
Entities: Book(id, title, author, category, price), User(id, name, email), Cart(id, userId), CartItem(id, cartId, bookId, quantity), Order(id, userId, status, totalAmount), Payment(id, orderId, method, status), Notification(id, orderId, type, status) Relationships: User 1:N Cart, Cart 1:N CartItem, CartItem N:1 Book, User 1:N Order, Order 1:1 Payment, Order 1:N Notification
Scaling Discussion
Bottlenecks
Tight coupling between payment methods and order processing limits adding new payment types
Notification logic mixed with order processing makes adding new notification channels hard
Single large classes handling multiple responsibilities become hard to maintain
Synchronous payment processing can slow down order placement under load
Solutions
Use dependency inversion and interfaces to decouple payment methods from order processing
Apply observer pattern for notifications to allow multiple independent notification handlers
Refactor classes to follow single responsibility principle for easier maintenance
Use asynchronous processing and message queues for payment and notification to improve responsiveness
Interview Tips
Time: Spend 10 minutes understanding requirements and clarifying scope, 20 minutes designing the system applying SOLID principles, 10 minutes discussing scaling and trade-offs, 5 minutes summarizing
Explain how each SOLID principle applies to the design
Show how interfaces and abstractions enable easy extension
Discuss separation of concerns and single responsibility in services
Highlight how dependency inversion reduces coupling
Describe patterns used like strategy for payments and observer for notifications
Mention how design supports maintainability and scalability

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