Bird
Raised Fist0
LLDsystem_design~7 mins

Event-driven design 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 components in a system are tightly coupled, a change or failure in one part can cause cascading failures or slow down the entire system. This tight coupling also makes it hard to add new features or scale parts independently, leading to slow development and fragile systems.
Solution
Event-driven design decouples components by having them communicate through events. When something happens, a component emits an event, and other components listen and react to those events independently. This allows parts of the system to work asynchronously and scale separately without waiting for each other.
Architecture
Producer
Event Broker
Consumer
Consumer

This diagram shows a producer sending events to an event broker, which then forwards those events to one or more consumers that have subscribed to them.

Trade-offs
✓ Pros
Decouples components, allowing independent development and deployment.
Improves scalability by enabling asynchronous processing and load distribution.
Enhances system resilience since failures in one component do not directly block others.
✗ Cons
Increases complexity in event management and debugging due to asynchronous flows.
Requires careful design of event schemas and handling of eventual consistency.
May introduce latency as events propagate through the system asynchronously.
Use when your system has multiple independent components that need to react to changes asynchronously, especially at scale above thousands of events per second or when you want to improve modularity and fault tolerance.
Avoid when your system requires strict synchronous operations or immediate consistency, or when the event volume is very low (under hundreds per second) making the added complexity unnecessary.
Real World Examples
Uber
Uber uses event-driven design to decouple services like ride requests, driver matching, and payment processing, allowing each to scale and evolve independently.
Netflix
Netflix uses event-driven architecture to handle user activity events for recommendations and monitoring, enabling real-time processing and fault isolation.
Amazon
Amazon employs event-driven design in its order processing system to asynchronously update inventory, payment, and shipping services.
Code Example
The before code shows OrderService calling payment and inventory services directly, creating tight coupling. The after code introduces an EventBus where OrderService publishes an 'order_created' event. PaymentService and InventoryService subscribe to this event and react independently, decoupling the components and enabling asynchronous processing.
LLD
### Before: tightly coupled synchronous calls
class OrderService:
    def create_order(self, order):
        payment_service.process_payment(order)
        inventory_service.update_stock(order)

### After: event-driven design with event bus
class EventBus:
    def __init__(self):
        self.listeners = {}

    def subscribe(self, event_type, listener):
        self.listeners.setdefault(event_type, []).append(listener)

    def publish(self, event_type, data):
        for listener in self.listeners.get(event_type, []):
            listener(data)

class OrderService:
    def __init__(self, event_bus):
        self.event_bus = event_bus

    def create_order(self, order):
        # Save order logic here
        self.event_bus.publish('order_created', order)

class PaymentService:
    def __init__(self, event_bus):
        event_bus.subscribe('order_created', self.process_payment)

    def process_payment(self, order):
        # Payment processing logic
        pass

class InventoryService:
    def __init__(self, event_bus):
        event_bus.subscribe('order_created', self.update_stock)

    def update_stock(self, order):
        # Stock update logic
        pass

# Usage
bus = EventBus()
payment_service = PaymentService(bus)
inventory_service = InventoryService(bus)
order_service = OrderService(bus)
order_service.create_order({'id': 123, 'items': ['book', 'pen']})
OutputSuccess
Alternatives
Request-Response
Components communicate synchronously by sending requests and waiting for responses.
Use when: Choose when operations require immediate results and tight coordination between components.
Batch Processing
Data is collected and processed in large groups at scheduled times rather than reacting to individual events.
Use when: Choose when real-time processing is not needed and throughput optimization is more important.
Summary
Event-driven design decouples system components by using events to communicate asynchronously.
It improves scalability, fault tolerance, and modularity by allowing independent processing.
This design requires careful event management and is best for systems needing asynchronous workflows at scale.

Practice

(1/5)
1. What is the main purpose of event-driven design in system architecture?
easy
A. To allow systems to react to actions as they happen asynchronously
B. To process all tasks sequentially in a fixed order
C. To store data permanently in a database
D. To create static web pages without user interaction

Solution

  1. Step 1: Understand event-driven design concept

    Event-driven design focuses on reacting to events or actions as they occur, rather than processing everything in a fixed sequence.
  2. Step 2: Compare options with concept

    To allow systems to react to actions as they happen asynchronously matches this idea by describing asynchronous reaction to actions. Other options describe unrelated concepts like sequential processing, data storage, or static content.
  3. Final Answer:

    To allow systems to react to actions as they happen asynchronously -> Option A
  4. Quick Check:

    Event-driven design = react asynchronously [OK]
Hint: Event-driven means reacting to events as they happen [OK]
Common Mistakes:
  • Confusing event-driven with sequential processing
  • Thinking event-driven is about data storage
  • Assuming event-driven means static content
2. Which of the following is the correct sequence in an event-driven system?
easy
A. Consumer -> Producer -> Queue
B. Producer -> Consumer -> Queue
C. Queue -> Producer -> Consumer
D. Producer -> Queue -> Consumer

Solution

  1. Step 1: Identify roles in event-driven flow

    Producers create events, queues hold events, and consumers process events.
  2. Step 2: Arrange correct order

    The correct order is Producer sends event to Queue, then Consumer reads from Queue.
  3. Final Answer:

    Producer -> Queue -> Consumer -> Option D
  4. Quick Check:

    Producer creates, Queue holds, Consumer processes [OK]
Hint: Events flow: Producer to Queue to Consumer [OK]
Common Mistakes:
  • Mixing up producer and consumer order
  • Placing queue after consumer
  • Ignoring the queue role
3. Consider this simplified event-driven code snippet:
event_queue = []

def produce(event):
    event_queue.append(event)

def consume():
    if event_queue:
        return event_queue.pop(0)
    return None

produce('A')
produce('B')
print(consume())
print(consume())
print(consume())

What is the output?
medium
A. None None None
B. B A None
C. A B None
D. A None B

Solution

  1. Step 1: Trace event production

    Two events 'A' and 'B' are added to the queue in order: ['A', 'B'].
  2. Step 2: Trace event consumption

    consume() removes and returns the first event: first 'A', then 'B', then None when empty.
  3. Final Answer:

    A B None -> Option C
  4. Quick Check:

    FIFO queue returns A then B then None [OK]
Hint: Queue pops first-in event first (FIFO) [OK]
Common Mistakes:
  • Assuming LIFO instead of FIFO
  • Forgetting to check empty queue
  • Mixing order of events
4. In an event-driven system, a developer wrote this code snippet:
def consume(event_queue):
    event = event_queue.pop()
    process(event)

What is the main issue with this code?
medium
A. It does not check if the queue is empty before popping
B. It adds events instead of removing them
C. It uses an undefined function 'process'
D. It processes events in reverse order, not FIFO

Solution

  1. Step 1: Analyze pop usage without check

    pop() removes last item but no check if queue is empty, risking error.
  2. Step 2: Identify error risk

    Calling pop() on empty list causes runtime error; code lacks safety check.
  3. Final Answer:

    It does not check if the queue is empty before popping -> Option A
  4. Quick Check:

    pop() on empty list causes error [OK]
Hint: Always check queue not empty before pop() [OK]
Common Mistakes:
  • Ignoring empty queue check
  • Confusing pop() order with error
  • Assuming process() is undefined error
5. You are designing a scalable event-driven system for a social media app. Which approach best improves scalability and fault tolerance?
hard
A. Store all events in a database and process them synchronously
B. Use a distributed message queue with multiple consumers processing events in parallel
C. Use a single queue and one consumer to ensure event order
D. Send events directly from producer to consumer without queue

Solution

  1. Step 1: Understand scalability and fault tolerance needs

    Social media apps have high event volume; parallel processing and fault tolerance are key.
  2. Step 2: Evaluate options for scalability

    Distributed queues with multiple consumers allow load balancing and fault tolerance. Single consumer limits throughput. Synchronous processing blocks system. Direct send lacks buffering and fault tolerance.
  3. Final Answer:

    Use a distributed message queue with multiple consumers processing events in parallel -> Option B
  4. Quick Check:

    Distributed queues + parallel consumers = scalable & fault tolerant [OK]
Hint: Parallel consumers on distributed queue scale best [OK]
Common Mistakes:
  • Choosing single consumer limits throughput
  • Ignoring asynchronous processing benefits
  • Skipping queue leads to lost events