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()
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
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
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)