Bird
Raised Fist0
LLDsystem_design~7 mins

SOLID violations and fixes in LLD - System Design Guide

Choose your learning style10 modes available
Problem Statement
When code mixes responsibilities, changes in one part cause unexpected bugs in others. Classes become hard to understand and maintain, slowing down development and increasing errors.
Solution
Apply SOLID principles to separate concerns, making each class responsible for a single task. This reduces dependencies and makes code easier to change, test, and extend without breaking existing functionality.
Architecture
BadDesign
(ManyRoles)
→Problems
↓
SOLIDApplied
(SingleRole)
→Benefits

This diagram shows how a design with many roles in one class leads to problems, while applying SOLID principles separates roles and brings benefits.

Trade-offs
✓ Pros
→
Improves code readability by separating concerns clearly.
→
Makes testing easier by isolating functionality.
→
Facilitates adding new features without breaking existing code.
→
Reduces bugs caused by unintended side effects.
✗ Cons
→
Initial design takes more time to plan and implement.
→
May increase number of classes, which can feel complex at first.
→
Requires developers to understand SOLID principles well.
Use when codebase grows beyond a few hundred lines or when multiple developers work on the same code. Essential for projects expected to evolve over time.
Avoid in very small scripts or prototypes where speed of writing is more important than maintainability.
Real World Examples
Amazon
Amazon applies SOLID principles in their microservices to keep services focused and independently deployable, reducing downtime during updates.
Netflix
Netflix uses SOLID to design modular components in their streaming platform, enabling quick feature additions without affecting core playback.
Uber
Uber structures their ride matching logic using SOLID to isolate responsibilities, making it easier to scale and maintain complex business rules.
Code Example
The before code mixes validation, saving, and emailing in one class, violating the Single Responsibility Principle. The after code separates these concerns into distinct classes, making each easier to maintain and test.
LLD
### Before (Violating SOLID - Single Responsibility Principle)
class UserManager:
    def __init__(self, user_data):
        self.user_data = user_data

    def save_user(self):
        # code to save user to database
        pass

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

    def validate_user(self):
        # code to validate user data
        pass


### After (Applying SOLID - Single Responsibility Principle)
class UserValidator:
    def validate(self, user_data):
        # code to validate user data
        pass

class UserRepository:
    def save(self, user_data):
        # code to save user to database
        pass

class EmailService:
    def send_welcome_email(self, user_data):
        # code to send email
        pass


# Usage
user_data = {...}
validator = UserValidator()
if validator.validate(user_data):
    repo = UserRepository()
    repo.save(user_data)
    email_service = EmailService()
    email_service.send_welcome_email(user_data)
OutputSuccess
Alternatives
Monolithic Design
Combines all responsibilities in fewer classes or modules without strict separation.
Use when: Choose when building very small or short-lived applications where simplicity and speed are priorities.
Procedural Programming
Focuses on functions and procedures rather than objects and classes.
Use when: Choose when working on simple scripts or tasks that do not require object-oriented design.
Summary
Violating SOLID principles causes tightly coupled code that is hard to maintain and extend.
Applying SOLID separates responsibilities, improving code clarity, testability, and flexibility.
SOLID is essential for scalable projects but should be balanced with simplicity in small scripts.

Practice

(1/5)
1. Which SOLID principle is violated if a class has too many responsibilities, making it hard to maintain?
easy
A. Single Responsibility Principle
B. Open/Closed Principle
C. Liskov Substitution Principle
D. Interface Segregation Principle

Solution

  1. Step 1: Understand the problem of multiple responsibilities

    A class with many responsibilities is hard to maintain and change because changes in one responsibility may affect others.
  2. Step 2: Identify the violated SOLID principle

    The Single Responsibility Principle states that a class should have only one reason to change, meaning one responsibility.
  3. Final Answer:

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

    One class, one responsibility = C [OK]
Hint: One class, one job means Single Responsibility Principle [OK]
Common Mistakes:
  • Confusing Single Responsibility with Open/Closed Principle
  • Thinking Liskov Substitution relates to responsibilities
  • Mixing Interface Segregation with class responsibilities
2. Which of the following code snippets correctly applies the Open/Closed Principle by allowing extension without modification?
class Shape {
draw() { /* draw shape */ }
}
class Circle extends Shape {
draw() { /* draw circle */ }
}
easy
A. Adding new shape classes by extending Shape without changing existing code
B. Modifying the Shape class draw method to handle all shapes
C. Using a single class for all shapes with if-else inside draw
D. Changing Circle class to override Shape's draw method with new logic

Solution

  1. Step 1: Understand Open/Closed Principle

    This principle says software entities should be open for extension but closed for modification.
  2. Step 2: Analyze the code snippet

    Extending Shape with new classes like Circle adds new behavior without changing existing Shape code.
  3. Final Answer:

    Adding new shape classes by extending Shape without changing existing code -> Option A
  4. Quick Check:

    Extend without modify = A [OK]
Hint: Extend classes, don't change existing ones [OK]
Common Mistakes:
  • Modifying base class instead of extending
  • Using if-else inside methods to handle new cases
  • Overriding methods incorrectly causing side effects
3. Given the following code violating Interface Segregation Principle, what is the output when calling robot.work()?
interface Worker {
void work();
void eat();
}
class Robot implements Worker {
public void work() { System.out.println("Robot working"); }
public void eat() { throw new UnsupportedOperationException(); }
}
Robot robot = new Robot();
robot.work();
medium
A. No output
B. UnsupportedOperationException thrown
C. Compilation error
D. Robot working

Solution

  1. Step 1: Understand the code behavior

    The robot class implements work() by printing "Robot working" and eat() throws an exception.
  2. Step 2: Analyze the method called

    Only robot.work() is called, which prints the message without error.
  3. Final Answer:

    Robot working -> Option D
  4. Quick Check:

    Calling work() prints message = A [OK]
Hint: Calling only implemented methods avoids exceptions [OK]
Common Mistakes:
  • Assuming eat() is called causing exception
  • Thinking interface violation causes compile error
  • Ignoring method actually called in code
4. Identify the main problem in this code violating Liskov Substitution Principle:
class Bird {
void fly() { System.out.println("Flying"); }
}
class Ostrich extends Bird {
void fly() { throw new UnsupportedOperationException(); }
}
medium
A. Bird class should not have fly method
B. Ostrich should override fly with different signature
C. Ostrich cannot replace Bird without breaking behavior
D. No violation, code is correct

Solution

  1. Step 1: Understand Liskov Substitution Principle

    Subtypes must be replaceable for their base types without altering correctness.
  2. Step 2: Analyze Ostrich behavior

    Ostrich throws exception on fly(), breaking expected behavior of Bird.
  3. Final Answer:

    Ostrich cannot replace Bird without breaking behavior -> Option C
  4. Quick Check:

    Subtype must behave like base = B [OK]
Hint: Subtype must not break base class behavior [OK]
Common Mistakes:
  • Thinking method signature change fixes violation
  • Ignoring exception breaking substitution
  • Assuming base class design is wrong here
5. You have a payment system where the PaymentProcessor interface has methods payWithCreditCard(), payWithPayPal(), and payWithCrypto(). A new payment method is added, but many classes implementing this interface do not support all methods, causing empty or error implementations. How should you fix this design following SOLID principles?
hard
A. Remove unsupported methods from implementations silently
B. Split the interface into smaller ones for each payment method
C. Keep one interface and add flags to check supported methods
D. Add default empty implementations to the interface methods

Solution

  1. Step 1: Identify the violated principle

    Having one interface with many methods causes classes to implement unsupported methods, violating Interface Segregation Principle.
  2. Step 2: Apply the fix

    Splitting the interface into smaller, specific interfaces lets classes implement only what they support, improving design.
  3. Final Answer:

    Split the interface into smaller ones for each payment method -> Option B
  4. Quick Check:

    Small interfaces, no unused methods = D [OK]
Hint: Use many small interfaces, not one big one [OK]
Common Mistakes:
  • Adding empty default methods hides design flaws
  • Using flags complicates code and violates design
  • Removing methods silently breaks interface contract
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