Why you need a queue, and when to use one
Some work is slow, bursty, or can fail: sending an email, resizing a photo, charging a card, updating a search index. If a web request does that work itself, the user waits, a traffic spike overwhelms the service, and a failure in a downstream system fails the whole request.
A message queue puts a buffer between the part that produces work and the part that consumes it. The producer drops a message in the queue and carries on. Consumers take messages out at their own pace.
It gives you two things interviewers always ask about:
- Resilience: the queue absorbs bursts and holds messages while a consumer is down, so work is not lost.
- Scalability: you add more consumers to share the work, without changing the producer.
Use a queue when:
- The caller does not need the result now: emails, notifications, reports, video processing.
- Traffic is bursty and the work behind it is slower than the peaks.
- Several systems must react to the same event (pub/sub).
- A downstream service is slow or unreliable and you must not lose the work.
Skip it, or use something else, when:
- The caller needs the answer straight away, such as loading a page or checking a password: use a direct call.
- The work is tiny and local: a function call is simpler than a queue.
- You only need work to run on a schedule: a scheduler or cron job may be enough.
- You cannot take on another system to run: a jobs table in your database can start simple.
Try it
Choose what sits between producers and the consumer, the kind of traffic, and whether the consumer crashes. Step through 16 ticks and watch the queue and the totals.
Messages between producers and a consumer
Step 1 of 17 · Press Space to play, arrow keys to step+0
0 waiting
0 done
This tick: none lost
Start: producers send messages to one consumer that can process 4 per tick. A queue sits between them and holds messages until the consumer is ready.
- waiting in queue:
- 0
- busiest queue:
- 0
- processed (total):
- 0
- lost (total):
- 0
- turned away (total):
- 0
- wait for a new message (ticks):
- 0
1 tick = one step of the simulation. The consumer can process 4 messages per tick. 'Wait for a new message' is how many ticks a newly arriving message would wait behind the backlog.
Things to try:
- Direct calls with Burst: the consumer can only take 4 per tick, so 24 requests fail. Switch to Queue: nothing is lost, and the queue peaks at 24 waiting.
- Look at the end of the Queue run: 16 messages are still waiting. Normal traffic (3 per tick) is only a little below the consumer's speed (4 per tick), so the backlog drains slowly. A queue absorbs a burst, but you may need more consumers to catch up.
- Tick Crash the consumer with Steady traffic. With Direct calls, 12 messages are lost. With Queue, none are lost: they wait, and the backlog is cleared after the consumer returns.
- Choose Bounded queue with back pressure with Burst: the queue never grows past 20, and 8 messages are turned away, so producers must slow down and retry. Nothing is lost, and memory use stays bounded.
Queue strategies
| Strategy | How it works | Good when | Weakness |
|---|---|---|---|
| Work queue (task queue) | Each message goes to one consumer in a group; consumers share the work | Jobs: email, image resizing, payments processing | No fan-out by itself; ordering is limited when many consumers run |
| Publish/subscribe (topic) | A message goes to every subscriber, each with its own copy | Several services react to the same event | Each subscriber needs its own queue and handling |
| Log-based stream | Messages are kept in an ordered log split into partitions; consumers track their position | Event streams, replay, very high volume, ordering per key | More to operate; ordering only within a partition |
| Delayed or scheduled messages | A message becomes visible only after a delay or at a time | Retries with backoff, reminders, timeouts | Needs broker support; many pending messages to hold |
Queue vs pub/sub
- Queue (point to point): each message is handled by one consumer. Add consumers to go faster. Use it for work: "resize this photo".
- Pub/sub (publish and subscribe): each message is delivered to every subscriber. Use it for events: "an order was placed", with the email service, the stock service and the analytics service each doing their own thing.
Many brokers combine them: one topic, several consumer groups. Each group gets every message, and inside a group the consumers share the work.
How acknowledgements, retries and delivery guarantees work
A consumer does not simply take a message. A safe loop looks like this:
- Receive a message. The broker keeps it, marked as in progress.
- Process it.
- Acknowledge (ack) it. Only now does the broker delete it.
- If the consumer crashes or times out before the ack, the broker redelivers the message to another consumer.
That gives at-least-once delivery: nothing is lost, but a message can be delivered more than once. So consumers must be idempotent: handling the same message twice has the same effect as handling it once. A common way is to store a unique message ID and skip any ID already done.
The three guarantees:
- At-most-once: acknowledge first, then process. A crash loses the message. Fast, but lossy.
- At-least-once: process, then acknowledge. A crash causes a duplicate. The usual default.
- "Exactly once": in practice, at-least-once delivery plus an idempotent consumer.
Retries and dead letters. If processing keeps failing, retry with a growing delay (backoff). After a limit, move the message to a dead-letter queue so a bad ("poison") message does not block everything, and alert someone to look at it.
Back pressure is the signal that tells producers to slow down when consumers cannot keep up, for example a bounded queue that rejects new messages when full. Without it, an unbounded queue grows until memory or disk runs out.
Queues vs the alternatives
| Concern | Message queue | Direct (synchronous) call | Jobs table in a database | Scheduled batch job |
|---|---|---|---|---|
| Caller waits for the work | No | Yes | No | No |
| Absorbs bursts | Yes | No | Yes | Yes, in batches |
| Survives a consumer being down | Yes | No | Yes | Yes |
| Extra moving part | Yes | No | No (uses your database) | Some |
| Latency before work starts | Small | None | Depends on polling | Until the next run |
| Best for | Background work and events | Answers needed now | Small systems starting out | Nightly reports |
A queue is the standard answer for background work and events. A direct call is right when the answer is needed now, and a jobs table is a fair start for a small system.
Avoiding a new single point of failure
The queue itself can fail, so treat it like a database:
- Run the broker as a cluster with copies of each queue, and spread them over failure zones.
- Make messages durable, saved to disk before the producer is told "accepted".
- Bound the queue and alert on depth and on the age of the oldest message, so a stuck consumer is seen early.
- Handle poison messages with retry limits and a dead-letter queue.
- Plan for the producer when the queue is down: retry, buffer briefly, or fail clearly.
The trade-off: a new system to run and a delay between "accepted" and "done", in return for resilience and scale.
Common mistakes
- An unbounded queue. A slow consumer lets it grow until it exhausts memory or disk. Set a limit and apply back pressure.
- Consumers that are not idempotent. A redelivery charges a card twice. Store an idempotency key and skip repeats.
- No dead-letter queue. One bad message is retried forever and blocks others. Move it aside after a few attempts and alert.
- Assuming order. With many consumers, messages can finish out of order. Use one partition per key when order matters.
- Not watching the backlog. A queue can look healthy while it quietly falls hours behind. Monitor depth and the age of the oldest message.
- Using a queue where the user waits for the result. Queues add delay. If the caller needs an answer now, call directly.
How to talk about message queues in an interview
- Say what goes on the queue and why: "sending the confirmation email is slow and can fail, so queue it."
- Name the guarantee and the fix: "at-least-once delivery, so the consumer is idempotent."
- Mention unprompted: retries with backoff, a dead-letter queue, back pressure, and monitoring depth and age.
- Say how you scale: "add consumers, and partition by key if ordering matters."
- Name the trade-off: resilience and scale in return for delay and another system to run.