Skip to content

Message Queues and Async Processing

Hand slow or bursty work to a queue so the caller does not wait and a failure does not lose it. Covers task queues, pub/sub, retries and back pressure.

intermediate30 minAsked oftenMessage queueAsyncPub/subBack pressureKafkaRetries

Last reviewed 24 Oct 2026 · by TheLearningLoop Team

Progress is saved on this device. Create a free account to keep it everywhere.

On this page ▾

Key takeaways

  • A queue sits between producers and consumers, so bursts are absorbed and a consumer failure does not lose messages.
  • Most queues deliver at least once, so consumers must be idempotent: handling the same message twice must be safe.
  • A queue only hides overload. Watch its depth, bound it, and add consumers or apply back pressure so it does not grow forever.

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
Between them:
Traffic:
Producers
+0

0 waiting

Consumer
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

StrategyHow it worksGood whenWeakness
Work queue (task queue)Each message goes to one consumer in a group; consumers share the workJobs: email, image resizing, payments processingNo fan-out by itself; ordering is limited when many consumers run
Publish/subscribe (topic)A message goes to every subscriber, each with its own copySeveral services react to the same eventEach subscriber needs its own queue and handling
Log-based streamMessages are kept in an ordered log split into partitions; consumers track their positionEvent streams, replay, very high volume, ordering per keyMore to operate; ordering only within a partition
Delayed or scheduled messagesA message becomes visible only after a delay or at a timeRetries with backoff, reminders, timeoutsNeeds 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:

  1. Receive a message. The broker keeps it, marked as in progress.
  2. Process it.
  3. Acknowledge (ack) it. Only now does the broker delete it.
  4. 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

ConcernMessage queueDirect (synchronous) callJobs table in a databaseScheduled batch job
Caller waits for the workNoYesNoNo
Absorbs burstsYesNoYesYes, in batches
Survives a consumer being downYesNoYesYes
Extra moving partYesNoNo (uses your database)Some
Latency before work startsSmallNoneDepends on pollingUntil the next run
Best forBackground work and eventsAnswers needed nowSmall systems starting outNightly 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.

Where you meet message queues in real systems

Anything that says "we will email you" or "your video is processing" almost certainly uses a queue. Here are three drawn out, the first with configuration you can run, then more examples.

Example 1: A queue of orders with a group of workers

When an order is placed, the web request drops a message on a queue and returns straight away. A group of workers takes messages off the queue to send emails, reserve stock and so on. Redis Streams and Kafka both do this, in different ways. Open the example below to see the picture and the commands.

Config: a queue and a group of workersSetup 2 short command listsCode no code changes
Producer addsthen moves onQueue holdsdurable, in orderWorker takes itone per messageAckthen it is deleted
The producer adds a message and carries on. A worker takes it, does the work, and acknowledges it. If a worker dies before acknowledging, the queue hands the message to another one.
# Redis Streams as a durable work queue with a consumer group.
# Create the stream and a group called "workers". "$" means start from new messages. MKSTREAM creates the stream if missing.
XGROUP CREATE orders workers $ MKSTREAM

# A producer adds a message. "*" lets Redis pick the id. The queue is capped at about 10000 entries.
XADD orders MAXLEN ~ 10000 * order_id 1001 total 750

# A consumer in the group asks for up to 1 new message (">" means never delivered to this group).
# Each message goes to exactly one consumer in the group, so adding consumers shares the work.
XREADGROUP GROUP workers worker-1 COUNT 1 STREAMS orders >

# After the work succeeds, acknowledge it. Unacknowledged messages stay pending and can be retried.
# Replace the id below with the one returned by XREADGROUP.
XACK orders workers 0-1

# See how many messages are waiting: the length of the stream and the pending ones.
XLEN orders
XPENDING orders workers

The Redis commands were run against a real Redis 7 server: creating the stream and group, adding a message, reading it as a consumer, acknowledging it, and checking the pending list. The Kafka commands were run against a real Apache Kafka container (a single broker, so the replication factor is 1): creating the topic, producing a keyed message, consuming it in a group and checking the lag. Names and sizes are examples.

Example 2: One event, several subscribers

When an order is placed, several services care: email, stock, analytics. With pub/sub the order service publishes one event, and each subscriber gets its own copy and handles it separately. New subscribers can be added without changing the publisher.

OrdersTopicEmailStockAnalytics
The order service publishes one event to a topic. The email, stock and analytics services each receive their own copy and process it independently, at their own speed.

Example 3: Retries and the dead-letter queue

Some messages fail again and again, for example because their data is malformed. Retrying forever would block the queue. The usual design retries a few times with growing delays, then sets the message aside in a dead-letter queue for a person or a repair job to look at.

Processing failsno ackRetry with backoffwait longer each timeLimit reachedfor example 5 triesDead-letter queuealert a person
Each failure is retried after a longer delay. After the retry limit, the message moves to a dead-letter queue and an alert fires, so one bad message does not block the others.

More real-world scenarios

4. Sending emails and notifications

A checkout should not wait for an email server. The order is saved, a message is queued, and a worker sends the email. If the email provider is down, the messages wait and are sent later.

5. Image and video processing

After a photo or video upload, thumbnails, resizing and encoding are queued as jobs. The upload returns at once, and workers process the files, scaling up when many uploads arrive.

6. Payment webhooks

Payment providers call your server to say a payment succeeded. A good design accepts the call quickly, puts it on a queue, and processes it with an idempotent worker, because the provider may send the same event more than once.

7. Event pipelines and logs

Applications emit events and logs to a stream. Several consumers read it: one for search, one for analytics, one for alerts. The stream decouples producers from all of them and can be replayed.

8. Ride dispatch and delivery

Ride and delivery apps receive a constant stream of location updates and requests. Queues and streams buffer them so the matching system sees an ordered, steady flow even when updates arrive in bursts.

9. IoT telemetry

Thousands of devices send readings. A queue accepts them quickly and lets the storage and analysis systems read at their own pace, so a slow database does not drop readings.

10. Bulk imports and exports

Uploading a large spreadsheet or exporting a year of data takes minutes. The request starts a job on the queue and returns a job ID, and the user is notified or can check progress.

Tools and managed services

KindNameNotes
Open sourceRabbitMQMessage broker with queues, routing and acknowledgements
Open sourceApache KafkaDistributed log for high-volume event streams
Open sourceRedis StreamsQueue and consumer groups inside Redis
AWSAmazon SQS, Amazon SNSManaged queues, and managed publish/subscribe
AWSAmazon KinesisManaged streaming data
Google CloudPub/SubManaged publish/subscribe messaging
AzureAzure Service BusManaged queues and topics
AzureAzure Event HubsManaged event streaming

The takeaway

Whenever a design has slow, bursty or failure-prone work the caller does not need to wait for, the first box you draw is a queue with several consumers, plus a plan for retries and back pressure.

Interview questions

Try answering out loud before you reveal hints. Interviewers care about how you reason, not just the final answer.

  1. 1.

    When would you put a message queue between two services instead of calling one from the other directly?

    easy
  2. 2.

    What does at-least-once delivery mean, and how should your consumer handle it?

    medium
  3. 3.

    Your queue depth keeps growing and the oldest message is two hours old. What do you do?

    medium
  4. 4.

    Messages for the same customer must be processed in order, but you want many consumers. How?

    medium
  5. 5.

    Design the notification system that sends an email, a push notification and an SMS after every order.

    medium

↑ Back to top

Next lesson