Bird
Raised Fist0
LLDsystem_design~7 mins

Anti-patterns to avoid in LLD - System Design Guide

Choose your learning style10 modes available

Start learning this pattern below

Jump into concepts and practice - no test required

or
Recommended
Test this pattern10 questions across easy, medium, and hard to know if this pattern is strong
Problem Statement
When developers follow common but flawed design habits, systems become hard to maintain, scale, and debug. These bad practices cause code duplication, tight coupling, and unpredictable behavior, leading to wasted time and costly fixes.
Solution
Recognizing and avoiding anti-patterns helps keep code clean, modular, and easy to change. By applying good design principles and patterns, developers create systems that are easier to understand, test, and extend over time.
Architecture
Spaghetti
Code
God Object
Hard to
Maintain

This diagram shows common anti-patterns leading to maintenance, testing, and fragility problems in software systems.

Trade-offs
✓ Pros
Avoiding anti-patterns improves code readability and maintainability.
Leads to easier debugging and faster feature additions.
Promotes modular design, enabling better testing and reuse.
✗ Cons
Requires upfront effort and discipline to recognize and prevent anti-patterns.
May slow initial development speed due to extra design considerations.
Learning curve for developers unfamiliar with good design principles.
Always use good design practices from the start, especially in projects expected to grow beyond a few thousand lines of code or multiple developers.
In very small, throwaway scripts or prototypes where long-term maintenance is not a concern.
Real World Examples
Amazon
Avoided monolithic god objects by adopting microservices, enabling independent scaling and deployment.
Netflix
Prevented tight coupling by using event-driven architecture, allowing services to evolve independently.
Uber
Reduced spaghetti code by enforcing clean service boundaries and clear API contracts.
Code Example
The before code mixes order types and payment logic, duplicating code and making changes risky. The after code uses the Strategy pattern to separate payment methods, making it easier to add new payment types without changing order processing.
LLD
### Before: Spaghetti code with duplicated logic and tight coupling
class OrderProcessor:
    def process(self, order):
        if order.type == 'online':
            print(f"Processing online order {order.id}")
            # duplicated payment logic
            if order.payment_method == 'card':
                print("Charging card")
            elif order.payment_method == 'paypal':
                print("Charging PayPal")
        elif order.type == 'store':
            print(f"Processing store order {order.id}")
            # duplicated payment logic
            if order.payment_method == 'card':
                print("Charging card")
            elif order.payment_method == 'paypal':
                print("Charging PayPal")

### After: Applying Strategy pattern to separate payment logic
class PaymentStrategy:
    def pay(self, order):
        pass

class CardPayment(PaymentStrategy):
    def pay(self, order):
        print("Charging card")

class PaypalPayment(PaymentStrategy):
    def pay(self, order):
        print("Charging PayPal")

class OrderProcessor:
    def __init__(self, payment_strategy: PaymentStrategy):
        self.payment_strategy = payment_strategy

    def process(self, order):
        print(f"Processing {order.type} order {order.id}")
        self.payment_strategy.pay(order)

# Usage
order = type('Order', (), {'id': 123, 'type': 'online', 'payment_method': 'card'})()
processor = OrderProcessor(CardPayment())
processor.process(order)
OutputSuccess
Alternatives
Modular Design
Breaks system into independent, well-defined modules instead of tangled code.
Use when: When you want easier maintenance and parallel development.
Design Patterns
Use proven solutions like Factory or Observer to solve common problems instead of ad-hoc code.
Use when: When facing recurring design challenges.
SOLID Principles
Guidelines to keep code flexible and decoupled, avoiding anti-patterns like god objects.
Use when: When building object-oriented systems requiring long-term maintainability.
Summary
Anti-patterns cause maintainability and scalability problems in software systems.
Avoiding them requires applying good design principles and patterns from the start.
Refactoring anti-patterns early improves code quality and reduces future costs.

Practice

(1/5)
1. Which of the following best describes the God Object anti-pattern in system design?
easy
A. Separating data storage and business logic into different layers.
B. A system design where components are loosely connected and communicate via events.
C. A single component that handles too many responsibilities, making the system hard to maintain.
D. Using multiple small services to handle different tasks independently.

Solution

  1. Step 1: Understand the God Object concept and compare options

    The God Object anti-pattern occurs when one component or class takes on too many responsibilities, leading to complex, hard-to-maintain code. A single component that handles too many responsibilities, making the system hard to maintain. matches this description exactly, while others describe good design practices.
  2. Final Answer:

    A single component that handles too many responsibilities, making the system hard to maintain. -> Option C
  3. Quick Check:

    God Object = Single overloaded component [OK]
Hint: God Object means one part does too much [OK]
Common Mistakes:
  • Confusing God Object with microservices
  • Thinking God Object is a good modular design
  • Mixing God Object with event-driven architecture
2. Which of the following is an example of a hardcoding anti-pattern in system design?
easy
A. Storing configuration values directly inside the source code.
B. Using environment variables for configuration.
C. Separating configuration into external files.
D. Using feature flags to toggle functionality.

Solution

  1. Step 1: Identify what hardcoding means and match options

    Hardcoding means embedding fixed values directly in the code, making changes difficult and error-prone. Storing configuration values directly inside the source code. shows storing config inside code, which is hardcoding. Others are best practices.
  2. Final Answer:

    Storing configuration values directly inside the source code. -> Option A
  3. Quick Check:

    Hardcoding = fixed values in code [OK]
Hint: Hardcoding means fixed values inside code [OK]
Common Mistakes:
  • Confusing hardcoding with using environment variables
  • Thinking external config files are hardcoding
  • Mixing feature flags with hardcoding
3. Consider a system where all modules directly access a single shared database without any abstraction layer. What is the main anti-pattern here?
medium
A. Tight Coupling
B. God Object
C. Spaghetti Architecture
D. Event-Driven Design

Solution

  1. Step 1: Analyze direct database access and identify the anti-pattern

    When modules directly access the database without abstraction, they become tightly coupled to the database schema. Tight Coupling means components depend heavily on each other, reducing flexibility and increasing maintenance difficulty.
  2. Final Answer:

    Tight Coupling -> Option A
  3. Quick Check:

    Direct DB access = Tight Coupling [OK]
Hint: Direct DB access causes tight coupling [OK]
Common Mistakes:
  • Confusing tight coupling with God Object
  • Thinking event-driven design fits here
  • Mixing spaghetti architecture with tight coupling
4. You find a system where many components are tightly interconnected with complex dependencies, making it hard to change one without breaking others. What anti-pattern is this, and how can you fix it?
medium
A. God Object; merge all components into one big class.
B. Spaghetti Architecture; refactor to modular design with clear interfaces.
C. Hardcoding; move all values into source code.
D. Tight Coupling; remove all interfaces and use direct calls.

Solution

  1. Step 1: Identify the anti-pattern from description and determine the fix

    Complex interdependencies causing fragility is typical of Spaghetti Architecture. Refactoring to modular design with clear interfaces reduces dependencies and improves maintainability.
  2. Final Answer:

    Spaghetti Architecture; refactor to modular design with clear interfaces. -> Option B
  3. Quick Check:

    Spaghetti Architecture = tangled dependencies [OK]
Hint: Tangled dependencies = spaghetti; modularize [OK]
Common Mistakes:
  • Thinking God Object means merging components
  • Confusing hardcoding with architecture issues
  • Believing removing interfaces reduces coupling
5. A startup built a monolithic system with many hardcoded values and a God Object managing most logic. They want to scale and maintain it easily. What is the best approach to fix these anti-patterns?
hard
A. Ignore scalability and focus only on adding new features.
B. Keep the monolith but add more hardcoded values for speed.
C. Merge all logic into one bigger God Object for simplicity.
D. Refactor into microservices, externalize configuration, and split responsibilities into smaller components.

Solution

  1. Step 1: Identify problems in current system and choose best solution to fix anti-patterns

    Monolith with hardcoded values and God Object causes poor scalability and maintainability. Refactoring into microservices splits responsibilities, externalizing config removes hardcoding, improving scalability and maintainability.
  2. Final Answer:

    Refactor into microservices, externalize configuration, and split responsibilities into smaller components. -> Option D
  3. Quick Check:

    Microservices + external config fix anti-patterns [OK]
Hint: Split monolith, externalize config, avoid God Object [OK]
Common Mistakes:
  • Thinking bigger God Object improves simplicity
  • Adding more hardcoding for speed
  • Ignoring scalability needs