Message Queues
The intuition: a café with order numbers
Picture a café at lunchtime. If the cashier grills every burger personally, the line will soon reach the door. So the cashier just takes the order, hands you a number, and clips the ticket to the rail. The cooks pull tickets one at a time and cook at their own pace. If more people show up, you can add another cook. The register doesn't need to change at all.
That rail of tickets is a message queue. The cashier is the producer, the cooks are the consumers. The customer hears "order received" right away, and the work gets done later. That's what asynchrony means.
Why go async
A queue solves three typical problems:
- Load spikes. The queue acts as a buffer: it absorbs the burst, and the workers work through it calmly. Without it, the spike hits the database directly, and the database goes down.
- Slow operations. Sending an email, generating a PDF, processing video, calling an external payment API. The user shouldn't wait 5 seconds while you render an image.
- Decoupling services. The order service publishes an "order created" event and has no idea who reads it: the warehouse, analytics, notifications. If the notification service is down, orders keep coming in anyway.
Tip: in the interview, name the response code. An async API returns
202 Acceptedwith a task id, and the client learns the status later (by polling or via a webhook). That instantly shows you understand the difference.
Sizing the buffer
Say a sale brings in 3,000 orders/s for 10 minutes, and all the workers together handle 1,000/s.
| Step | Calculation | Result |
|---|---|---|
| Queue growth during the peak | 3,000 − 1,000 | 2,000 msg/s |
| Backlog after 10 minutes | 2,000 × 600 s | 1.2M messages |
| After the peak, normal 300/s comes in; spare capacity | 1,000 − 300 | 700 msg/s |
| Time to drain | 1,200,000 / 700 | ≈ 29 minutes |
The table shows that the queue kept the system from falling over, but processing latency grew to half an hour. If that's unacceptable, you need worker autoscaling based on queue length. That's a strong line to drop in an interview.
Task queue vs event log
The word "queue" hides two different models.
| Task queue (RabbitMQ, SQS) | Event log (Kafka) | |
|---|---|---|
| After reading | Message is deleted after ack | Stays in the log for the retention period |
| Who reads | Each task goes to one worker | Many independent groups read the same data |
| Replaying the past | Not possible | Possible: move the offset back |
| Ordering | Not guaranteed with multiple workers | Guaranteed within a partition |
| Routing | Flexible (exchange, routing key) | Simple: topic and partition |
| Typical use | Background jobs: emails, image resizing | Event streams, analytics, event sourcing |
| Throughput | Thousands to tens of thousands/s | Hundreds of thousands to millions/s |
In short: RabbitMQ means "do this work," Kafka means "here's what happened, read it if you care."
Partitions and message ordering
A Kafka topic is split into partitions. Each partition is an ordered log where every message has a number (offset). Partitions give you parallelism: you can spread them across brokers and read them at the same time.
Ordering is guaranteed only within a single partition. So messages whose order matters are sent with the same key. The partition is picked as hash(key) % N. If you use order_id as the key, the "created → paid → shipped" events of one order land in the same partition and never get mixed up.
{"values": ["created #7", "paid #7", "created #9", "shipped #7", "paid #9", "created #12"], "pointers": {"analytics": 2, "notifications": 5}, "highlight": [0, 1, 3], "caption": "Partition 0: order #7 events go strictly in order; two groups read the log independently, each with its own offset"}Consumer groups
A consumer group is a set of consumers that read a topic together.
- Within a group, each partition goes to exactly one consumer. That's how the work is split among them.
- Different groups read the topic independently, each with its own offset. Analytics can lag an hour behind while notifications run in real time.
- Having more consumers in a group than partitions is pointless: the extras just sit idle. So you plan the partition count with headroom, because increasing it later hurts:
hash % Nchanges, and ordering breaks.
Dead Letter Queue
Sometimes a message just can't be processed: broken JSON, a deleted user, a bug in the code. If you keep putting it back in the queue forever, you get a poison message: it blocks the partition or burns CPU endlessly.
Here's the fix. After N failed attempts with exponential backoff, the message is moved to a DLQ, a separate queue for the "problem children." You put an alert on it, investigate by hand, and replay after the fix.
retry:
max_attempts: 5
backoff: exponential # 1s, 2s, 4s, 8s, 16s
dead_letter_queue: orders.dlqHow it sounds in an interview
"I'll make order creation asynchronous: the API puts an event into Kafka and returns 202. The partition key is
order_id, to keep the events of one order in sequence. 32 partitions, with headroom for more workers. Delivery is at-least-once, so the consumers are idempotent: they check whether thisevent_idhas already been processed. Failed messages go to a DLQ with an alert after 5 retries. I scale the workers based on consumer lag."
Be sure to name the trade-offs:
- Eventual consistency. For a while, the user sees a "processing" status.
- Complexity. You get one more component, lag monitoring, and debugging of distributed chains.
- Duplicates. A message can arrive twice, so you need idempotency.
Common mistakes
- Assuming Kafka guarantees global ordering. It only guarantees ordering within a partition.
- Forgetting about idempotency. Exactly-once "out of the box" almost never happens: retries and rebalances bring duplicates.
- Not thinking through a DLQ. Then one broken message can stall all processing.
- Putting a queue everywhere. If the user needs an answer right now (login check, account balance), a synchronous call is more honest and simpler.
- Adding more consumers than partitions and expecting things to speed up.
If you can confidently explain in an interview why you need a queue here, which one to choose, and what happens when something fails, you already sound like an engineer who has shipped this.