System DesignIntermediate7 min readPart 9 of 14

Message Queues

Message queues let services talk asynchronously: a producer puts a message on a queue, and a consumer handles it later. This decoupling is fundamental to building distributed systems that scale and survive failures.

Can you explain message queues out loud?

I'm Alex. Read this, then I'll interview you on a design problem and push on the parts you skip.

Why message queues?Core idea

%%{init: {"look":"handDrawn","handDrawnSeed":7,"theme":"base","fontFamily":"Virgil, \"Segoe Print\", \"Comic Sans MS\", cursive","themeVariables":{"fontFamily":"Virgil, \"Segoe Print\", \"Comic Sans MS\", cursive","fontSize":"16px","primaryColor":"#fff4e6","primaryBorderColor":"#1e1e1e","primaryTextColor":"#1e1e1e","secondaryColor":"#e7f5ff","tertiaryColor":"#ebfbee","lineColor":"#1e1e1e","textColor":"#1e1e1e","mainBkg":"#fff4e6","nodeBorder":"#1e1e1e","clusterBkg":"#f8f9fa","edgeLabelBackground":"#ffffff","actorBkg":"#e7f5ff","actorBorder":"#1e1e1e","noteBkgColor":"#fff9db","noteBorderColor":"#1e1e1e"}}}%%
graph LR
  P["Producer<br/>(order service)"] -->|"order created"| Q["Message queue"]
  Q --> C1["Payment"]
  Q --> C2["Inventory"]
  Q --> C3["Email"]
  class P blue
  class Q orange
  class C1,C2,C3 green
classDef orange fill:#ffd8a8,stroke:#1e1e1e,color:#1e1e1e
classDef blue fill:#a5d8ff,stroke:#1e1e1e,color:#1e1e1e
classDef green fill:#b2f2bb,stroke:#1e1e1e,color:#1e1e1e
classDef red fill:#ffc9c9,stroke:#1e1e1e,color:#1e1e1e
classDef violet fill:#d0bfff,stroke:#1e1e1e,color:#1e1e1e
classDef yellow fill:#ffec99,stroke:#1e1e1e,color:#1e1e1e
classDef grey fill:#e9ecef,stroke:#1e1e1e,color:#1e1e1e
DiagramThe producer publishes once; each consumer handles it independently.

Decoupling

Services don’t need to know about each other. Add a new consumer without touching the producer.

Buffering

If messages arrive faster than they can be handled, the queue absorbs the burst.

Async processing

Slow work (image processing, sending email) runs in the background instead of blocking the request.

Fan-out

One message can be read by several services at once (the publish/subscribe or “pub/sub” pattern).

Order processing exampleExample

%%{init: {"look":"handDrawn","handDrawnSeed":7,"theme":"base","fontFamily":"Virgil, \"Segoe Print\", \"Comic Sans MS\", cursive","themeVariables":{"fontFamily":"Virgil, \"Segoe Print\", \"Comic Sans MS\", cursive","fontSize":"16px","primaryColor":"#fff4e6","primaryBorderColor":"#1e1e1e","primaryTextColor":"#1e1e1e","secondaryColor":"#e7f5ff","tertiaryColor":"#ebfbee","lineColor":"#1e1e1e","textColor":"#1e1e1e","mainBkg":"#fff4e6","nodeBorder":"#1e1e1e","clusterBkg":"#f8f9fa","edgeLabelBackground":"#ffffff","actorBkg":"#e7f5ff","actorBorder":"#1e1e1e","noteBkgColor":"#fff9db","noteBorderColor":"#1e1e1e"}}}%%
sequenceDiagram
  participant Client
  participant OrderSvc as Order service
  participant Q as Kafka
  participant Pay as Payment
  participant Inv as Inventory
  participant Mail as Email
  Client->>OrderSvc: place order
  OrderSvc->>Q: publish order-created
  OrderSvc-->>Client: order confirmed
  Q->>Pay: process payment
  Q->>Inv: reserve inventory
  Q->>Mail: send confirmation
  Note over Pay,Mail: all three consume independently
SequenceThe user gets “order confirmed” right away; payment, inventory and email happen after.

Kafka vs RabbitMQCompare

Apache Kafkaevent streaming

Messages are stored in topics, split into partitions for parallelism, and kept for a set time even after they are read. Consumers read at their own pace. Best for event sourcing, log aggregation, real-time analytics and high-throughput pipelines.

RabbitMQmessage broker

A traditional broker: messages are deleted once consumed. It supports rich routing (topic and header exchanges), priorities and TTLs. Best for task queues, RPC-style requests and notifications.

FeatureKafkaRabbitMQ
ModelEvent streamingMessage broker
RetentionConfigurable (days or weeks)Until consumed
OrderingWithin a partitionPer queue
ThroughputMillions per secondTens of thousands per second
Use caseLog aggregation, analytics, event sourcingTask queues, RPC, notifications

Delivery guaranteesHow it works

At-most-oncemay lose

Send once, never retry. Fast, but messages can be lost. Fine for logs and metrics.

At-least-oncemay duplicate

Retry until the consumer acknowledges. Nothing is lost, but duplicates happen, so the consumer must be idempotent. The most common choice.

Exactly-oncehard

Each message takes effect once. Very hard in general; Kafka offers it within one session using its transactional APIs.

Message orderingHow it works

Kafka keeps messages in order within a partition, not across partitions. If one user's messages must be handled in order, send them all to the same partition by using the user ID as the partition key.

The partition key keeps one user’s events in orderTypeScript
producer.send({
  topic: "order-events",
  key: userId,    // same userId → same partition → in order
  value: { orderId, action: "created" },
});

Dead-letter queuesDesign

A dead-letter queue (DLQ)catches messages that still fail after the maximum number of retries. Without one, a single bad message (a “poison pill”) can block the whole queue.

%%{init: {"look":"handDrawn","handDrawnSeed":7,"theme":"base","fontFamily":"Virgil, \"Segoe Print\", \"Comic Sans MS\", cursive","themeVariables":{"fontFamily":"Virgil, \"Segoe Print\", \"Comic Sans MS\", cursive","fontSize":"16px","primaryColor":"#fff4e6","primaryBorderColor":"#1e1e1e","primaryTextColor":"#1e1e1e","secondaryColor":"#e7f5ff","tertiaryColor":"#ebfbee","lineColor":"#1e1e1e","textColor":"#1e1e1e","mainBkg":"#fff4e6","nodeBorder":"#1e1e1e","clusterBkg":"#f8f9fa","edgeLabelBackground":"#ffffff","actorBkg":"#e7f5ff","actorBorder":"#1e1e1e","noteBkgColor":"#fff9db","noteBorderColor":"#1e1e1e"}}}%%
graph LR
  Q["Main queue"] --> C["Consumer"]
  C -->|"max retries exceeded"| DLQ["Dead-letter queue"]
  DLQ --> A["Alert the team"]
  class Q orange
  class C green
  class DLQ red
  class A yellow
classDef orange fill:#ffd8a8,stroke:#1e1e1e,color:#1e1e1e
classDef blue fill:#a5d8ff,stroke:#1e1e1e,color:#1e1e1e
classDef green fill:#b2f2bb,stroke:#1e1e1e,color:#1e1e1e
classDef red fill:#ffc9c9,stroke:#1e1e1e,color:#1e1e1e
classDef violet fill:#d0bfff,stroke:#1e1e1e,color:#1e1e1e
classDef yellow fill:#ffec99,stroke:#1e1e1e,color:#1e1e1e
classDef grey fill:#e9ecef,stroke:#1e1e1e,color:#1e1e1e
DiagramFailing messages are parked for a human instead of blocking everything behind them.

Common interview mistakesPitfalls

No retry planA failed message needs a retry count and, eventually, a dead-letter queue.
Ignoring poison pillsOne malformed message that crashes the consumer can block the queue.
Kafka for a simple task queueIt is overkill; RabbitMQ or SQS is simpler.
Bad partitioningAn uneven partition key makes one consumer do most of the work.
No monitoringTrack queue depth, consumer lag and processing time.

In the interviewIn the interview

Frequently asked questionsFAQ

When should you add a message queue to a design?

Add a queue when you need to decouple producers from consumers, smooth traffic bursts, or run work that can be handled later. The canonical cases are async jobs like email, heavy fan-out, and pipeline stages where a slow stage must not block the request path.

What is the difference between at-least-once and exactly-once delivery?

At-least-once retries every message until it is acknowledged, so consumers must be idempotent to handle duplicates. Exactly-once removes duplicates but needs transactional or deduplicated consumers and costs throughput. Most systems pick at-least-once plus consumer-side dedup, which behaves like once in practice.

How does a queue keep ordering across multiple partitions?

Systems like Kafka keep messages ordered within a partition, not globally across all partitions. If global order matters, use a single partition or route related messages to the same partition with a shared key; otherwise accept per-partition ordering to gain parallelism.

Now explain message queues to me out loud

I'll give you a design problem and push on the trade-offs, like a real interviewer.

Practice with Alex

Keep readingKeep reading

Ready to practice?

Reading is step one. Saying it out loud is step two.

Start a mock interview with Alex, get pushed on the parts you skip, and see your hiring signal right after.