LearnThatStack Ace your next interview

Message Queues System Design.
Cheat sheet.

Quick reference for Message Queues System Design - sectioned for fast scanning. Skim the part you're shaky on, walk in confident.

System Design Concepts 16-section reference ~4 min read

Summary

Master message queue concepts for system design interviews. Covers delivery patterns, guarantees, popular technologies (RabbitMQ, Kafka, SQS), and implementation patterns. Essential for designing scalable, decoupled systems that handle asynchronous communication and workload distribution.

1. What are Message Queues?

Definition: A message queue is a form of asynchronous service-to-service communication used in distributed systems. Messages are stored on the queue until they are processed and deleted.

Key Characteristics:

  • Asynchronous: Sender doesn't wait for receiver
  • Decoupled: Producer and consumer don't need to know about each other
  • Reliable: Messages persist until processed
  • Scalable: Can handle varying loads

2. Why Use Message Queues?

Benefits

  • Decoupling: Services can evolve independently
  • Scalability: Handle traffic spikes by buffering messages
  • Reliability: Messages aren't lost if consumer is down
  • Flexibility: Add/remove consumers dynamically
  • Order guarantees: FIFO processing (in some systems)

Use Cases

  • Task processing: Background jobs, image processing
  • Event streaming: User activity, system events
  • Communication: Service-to-service messaging
  • Load leveling: Smooth out traffic spikes
  • Batch processing: Aggregate and process in batches

3. Core Concepts

Message Components

Message {
    id: unique_identifier
    body: actual_data
    metadata: {
        timestamp: when_created
        priority: high/medium/low
        ttl: time_to_live
        retry_count: number_of_attempts
    }
}

Key Terminology

  • Producer/Publisher: Sends messages to queue
  • Consumer/Subscriber: Receives and processes messages
  • Broker: Manages message storage and delivery
  • Queue: Storage for messages
  • Topic: Category for pub/sub messaging
  • Exchange: Routes messages to queues (RabbitMQ)

4. Message Delivery Patterns

1. Point-to-Point (Queue)

  • One producer, one consumer
  • Each message processed by exactly one consumer
  • Message removed after processing

2. Publish-Subscribe (Topic)

  • One producer, multiple consumers
  • Each consumer gets a copy of the message
  • Used for broadcasting events

3. Request-Reply

  • Synchronous pattern over async infrastructure
  • Correlation ID links request and response

5. Delivery Guarantees

At-Most-Once

  • Message delivered 0 or 1 time
  • Fast but may lose messages
  • Use when: Performance critical, occasional loss acceptable

At-Least-Once

  • Message delivered 1 or more times
  • May have duplicates
  • Use when: Can't afford to lose messages, can handle duplicates

Exactly-Once

  • Message delivered exactly 1 time
  • Most complex to implement
  • Use when: Critical transactions, can't handle duplicates

6. Common Patterns

1. Work Queue Pattern

# Producer
queue.send(task_data)

# Multiple workers
while True:
    task = queue.receive()
    process(task)
    queue.ack(task)

2. Fanout Pattern

# Publisher
exchange.publish(event, routing_key='')

# Multiple subscribers get copy
subscriber1.on_message(handler1)
subscriber2.on_message(handler2)

3. Dead Letter Queue (DLQ)

  • Failed messages moved to separate queue
  • Prevents poison messages from blocking processing
  • Enables debugging and manual intervention

4. Priority Queue

# High priority message
queue.send(message, priority=10)

# Normal priority
queue.send(message, priority=5)

8. Design Considerations

Performance Factors

  • Throughput: Messages per second
  • Latency: End-to-end delivery time
  • Storage: Message retention period
  • Batch size: Process individually vs batches

Scalability Strategies

  1. Horizontal scaling: Add more consumers
  2. Partitioning: Split queue across nodes
  3. Sharding: Distribute by message key
  4. Consumer groups: Load balance across consumers

Reliability Measures

  • Persistence: Disk vs memory storage
  • Replication: Multiple copies for failover
  • Acknowledgments: Confirm message processing
  • Monitoring: Track queue depth, processing rate

9. Implementation Best Practices

Producer Side

# Retry logic
def send_with_retry(queue, message, max_retries=3):
    for i in range(max_retries):
        try:
            queue.send(message)
            return True
        except Exception as e:
            if i == max_retries - 1:
                raise
            time.sleep(2 ** i)  # Exponential backoff

Consumer Side

# Idempotent processing
def process_message(message):
    # Check if already processed
    if is_processed(message.id):
        return
    
    # Process message
    result = do_work(message)
    
    # Mark as processed
    mark_processed(message.id)
    
    # Acknowledge
    message.ack()

Error Handling

  1. Retry with backoff: Temporary failures
  2. Dead letter queue: Permanent failures
  3. Circuit breaker: Prevent cascading failures
  4. Monitoring/Alerting: Track failure rates

10. Common Interview Questions

System Design Questions

  1. Design a distributed task queue (like Celery)

    • Components: Broker, Workers, Result backend
    • Consider: Routing, priorities, retries
  2. Design a notification system

    • Multiple channels (email, SMS, push)
    • Rate limiting, batching
    • Delivery guarantees
  3. Design a real-time chat system

    • Pub/sub for online users
    • Queue for offline delivery
    • Message ordering

Technical Questions

  1. How to handle duplicate messages?

    • Idempotency keys
    • Message deduplication
    • Exactly-once semantics
  2. How to ensure message ordering?

    • Single consumer per partition
    • Message keys for related messages
    • Sequence numbers
  3. How to handle backpressure?

    • Rate limiting producers
    • Dynamic scaling consumers
    • Circuit breakers

11. Trade-offs Cheat Sheet

Aspect Synchronous Message Queue
Coupling Tight Loose
Latency Low Higher
Complexity Simple More complex
Scalability Limited High
Reliability Depends High

12. Quick Decision Framework

Choose Message Queues When:

  • ✓ Need to decouple services
  • ✓ Handle traffic spikes
  • ✓ Process tasks asynchronously
  • ✓ Build event-driven architecture
  • ✓ Need reliability guarantees

Avoid When:

  • ✗ Need real-time synchronous response
  • ✗ Simple request-response sufficient
  • ✗ Added complexity not justified

13. Performance Numbers (Rough Estimates)

  • RabbitMQ: 20k-50k msg/sec per node
  • Kafka: 100k-1M msg/sec per broker
  • SQS: 300k msg/sec (soft limit)
  • Redis Pub/Sub: 1M msg/sec (limited by network)

14. Final Interview Tips

  1. Start simple: Basic queue, then add complexity
  2. Consider trade-offs: No perfect solution
  3. Think about failures: What happens when X fails?
  4. Discuss monitoring: Queue depth, lag, errors
  5. Scale calculations: Show throughput math
  6. Real examples: Relate to actual use cases

Quick Reference Code

Basic Producer/Consumer (Python)

# RabbitMQ example
import pika

# Producer
connection = pika.BlockingConnection()
channel = connection.channel()
channel.queue_declare(queue='tasks')
channel.basic_publish('', 'tasks', 'Hello')

# Consumer
def callback(ch, method, properties, body):
    process(body)
    ch.basic_ack(method.delivery_tag)

channel.basic_consume('tasks', callback)
channel.start_consuming()

Kafka Example

# Producer
producer = KafkaProducer()
producer.send('topic', value=b'message')

# Consumer
consumer = KafkaConsumer('topic')
for message in consumer:
    process(message.value)

Remember: In interviews, focus on understanding requirements first, then build up the design incrementally. Always discuss trade-offs and be prepared to deep dive into any component.

Found this useful? Pass it on.
Pro · $10/mo

The sheet is free. Pro goes deeper.

Pro opens the full question library behind every sheet, every refresher and a monthly AI allowance. One subscription, all formats.

Full question library All refreshers Cancel anytime