DEV Community

Saras Growth Space
Saras Growth Space

Posted on

LLD Object-Oriented Design: Design Mistakes That Break Systems (and How to Avoid Them)

After learning how to structure systems with layers and handle cross-cutting concerns, the next step is understanding what goes wrong in real designs.

Most LLD failures are not because of missing knowledge, but because of poor design choices that accumulate over time.


1. Fat Services (God Objects)

One of the most common problems in LLD:

A single service doing too many things.


Example

class OrderService:
    def place_order(self):
        self.validate()
        self.pay()
        self.update_inventory()
        self.send_notification()
        self.log()
Enter fullscreen mode Exit fullscreen mode

Why this is bad:

  • violates Single Responsibility Principle
  • becomes hard to test
  • any change risks breaking everything
  • logic cannot be reused

Correct Approach:

Split responsibilities into:

  • PaymentService
  • InventoryService
  • NotificationService
  • OrderService (orchestrator only)

2. Wrong Inheritance Usage

Inheritance is often misused for convenience.


Bad Example

class Bird:
    def fly(self):
        pass

class Penguin(Bird):
    pass
Enter fullscreen mode Exit fullscreen mode

Problem:

  • Penguin cannot fly
  • but is forced to inherit behavior

This breaks system correctness.


Fix:

Use composition or interfaces instead of forced inheritance.


3. Anemic Domain Models

This happens when:

Classes only hold data but contain no behavior.


Example

class Order:
    def __init__(self, status):
        self.status = status
Enter fullscreen mode Exit fullscreen mode

All logic is outside the class.


Problem:

  • business rules are scattered
  • objects are just data containers
  • system becomes procedural instead of object-oriented

Better Approach:

Put behavior inside entities:

  • Order should manage its own state transitions

4. Tight Coupling Between Layers

When services directly depend on concrete implementations:


Bad Design

  • Service directly calls database
  • Service directly calls external APIs

Problem:

  • changes ripple across system
  • testing becomes difficult
  • system becomes rigid

Fix:

Use:

  • interfaces
  • dependency injection
  • abstraction layers

5. Breaking Layer Boundaries

A very common mistake:

Business logic leaking into controllers or infrastructure.


Example:

  • validation inside controllers
  • DB logic inside services
  • external API logic inside entities

Result:

  • unclear responsibilities
  • messy architecture
  • hard to scale system

6. Over-Engineering Early

Another major mistake:

  • adding factories everywhere
  • using patterns without need
  • creating unnecessary abstractions

Problem:

  • complexity increases without benefit
  • code becomes hard to read
  • development slows down

Core Insight

Good design is not about adding patterns—it is about placing responsibilities correctly.


How to Identify Bad Design Early

Ask these questions:

  • Does one class have too many responsibilities?
  • Can I change one part without affecting others?
  • Are rules scattered or centralized?
  • Is behavior inside the correct object?

If answers are unclear, design is likely flawed.


Real System Impact

Bad design leads to:

  • slow feature development
  • frequent bugs
  • difficulty scaling system
  • high maintenance cost

Good design leads to:

  • predictable behavior
  • easy extensions
  • clean separation of concerns

Design Principle

A system is well-designed when each component has a clear, minimal, and well-defined responsibility.


One-Line Takeaway

Most system failures come from unclear responsibilities, not missing code.

Top comments (0)