Part 3 · The Building Blocks

Message Queues: A Gentle Introduction

Placing an order shouldn't wait on the email provider — or fail because SMS is down. How message queues let you do the essential work now and everything else reliably a moment later, with acks, retries, dead-letter queues and back pressure.

Lesson 187 min readbeginner#queues#async

On this page

  1. The Problem
  2. The Core Idea
  3. How It Works
  4. Trade-offs
  5. In the Real World
  6. Interview Questions
  7. Key Takeaways
  8. Further Reading

The Problem

A user places an order. Your API then has to:

  1. save the order,
  2. charge the card,
  3. send a confirmation email,
  4. send an SMS,
  5. notify the warehouse,
  6. update loyalty points,
  7. update recommendations.

If you do all of this inside the request, the user waits 4 seconds. If the email provider is slow, the user waits even longer. And if the SMS service is down, the whole order fails, even though the order and payment were fine.

And during a flash sale, 20,000 orders arrive in one minute. Every downstream system gets hammered at once.

Message queues solve this. They let you do the essential work now and everything else reliably, a moment later.

A message queue is a service that holds messages (small pieces of data that describe a job) in order, until another program is ready to take and process them. An API is the set of web addresses that clients call to use your app. Downstream means the other systems that your service calls, such as the email service.

The Core Idea

Think about the order ticket rail in a restaurant kitchen.

  • The waiter takes your order, clips a ticket to the rail, and goes back to serving customers. They don't stand in the kitchen waiting for your food.
  • The cooks take tickets from the rail one at a time, as fast as they can.
  • On a busy night, tickets pile up on the rail, but nothing is lost. The cooks work through them.
  • If one cook goes on break, the others keep taking tickets.

A message queue is that ticket rail:

  • Producers (the waiter) are programs that put messages (tickets) on the queue.
  • Consumers or workers (the cooks) are programs that take messages off and process them.
  • The queue holds messages safely until they're processed.

How It Works

The basic flow

Diagram
                           ┌─────────────────────┐
[API: place order] ──msg──►│ Queue: order-events │──► [Worker 1] send email
   (producer)              │ ■ ■ ■ ■ ■           │──► [Worker 2] send email
                           └─────────────────────┘──► [Worker 3] send email
                                                        (consumers)

For the order example:

step 1 / 8
User
API
Queue
Workers
  1. POST /orders
  2. ↻ save order + charge card
  3. publish order_placed {orderId: 5521}
  4. “Order placed!”
    ~200 ms
  5. Seconds later, in the background
  6. order_placed 5521
  7. ↻ email · SMS · warehouse · loyalty points
  8. ack → message deleted
Placing an order with a queue. The user waits only for the part that must happen now. · sequence · step through

The user gets a fast response, and the slow or unreliable tasks happen in the background. "Publish" means "put a message on the queue". "Ack" is short for acknowledgement, a note from the worker that says "I finished this message".

Why queues help

1. Faster responses. The user only waits for the essential work.

2. Decoupling. Decoupling means that parts of the system do not depend on each other directly. The API doesn't need to know about the email service, the SMS service or the warehouse system. It just publishes "order placed". You can add a new consumer (say, a fraud-check service) without changing the API.

3. Resilience. If the email service is down for 10 minutes, messages wait in the queue. When it recovers, workers process the backlog, and nothing is lost. Without a queue, those 10 minutes of emails would simply fail.

4. Smoothing traffic spikes (load levelling). A spike is a sudden burst of traffic. During a flash sale, orders arrive at 20,000 per minute, but the warehouse system can only handle 2,000 per minute. The queue absorbs the spike, and workers process at a steady, safe rate. The queue grows during the rush and drains afterwards.

Load levelling — the queue absorbs the spike
Incoming:   ▁▁▁█████████▁▁▁▁▁▁   (flash-sale spike)
Queue:      ▁▁▁▂▄▆███▇▅▃▂▁▁▁▁   (grows, then drains)
Processing: ▃▃▃▃▃▃▃▃▃▃▃▃▃▃▃▃▃   (steady, at the rate downstream can bear)

5. Easy scaling. Queue getting long? Add more workers. Many workers taking from the same queue is called the competing consumers pattern (the workers compete for messages). Each message goes to just one of them.

Acknowledgements: not losing messages

What if a worker takes a message and then crashes halfway through sending the email? Queues handle this with acknowledgements (acks):

  1. Receive
    worker takes the message; the queue hides it from other workers
  2. Finish + ACK
    the queue deletes it ✅
  3. Crash before ACK
    nobody confirms it…
  4. Visibility timeout passes
    the message reappears and another worker picks it up 🔁 — so it may run twice
Acknowledgements. A message is only deleted once a worker says it's done. · timeline

In Amazon SQS (Simple Queue Service, a queue run by AWS), this timeout is called the visibility timeout. In RabbitMQ (a popular open-source message broker, which is a server that routes messages), if a consumer disconnects without acking, the message is redelivered.

There's an important consequence: a message might be processed more than once. For example, the worker sent the email, then crashed before acking. So consumers should be safe to run twice (idempotent). For example, record "email sent for order 5521" and skip it if it's already done. Idempotent means that doing the same thing twice gives the same result as doing it once. Part 6 goes deep on this.

When things keep failing

Some messages will fail over and over: bad data, a bug, a missing record. If they're retried forever, they waste resources and can block other work. The usual solution:

  • Retry a few times, with increasing delays.
  • Then move the message to a dead-letter queue (DLQ), a separate queue for "problem" messages, which engineers can inspect and fix. "Dead letter" is an old post-office term for a letter that cannot be delivered.

(More in Part 6.)

  1. 1
    Attempt 1
    fails — bad data, bug, missing record
  2. 2
    Retry after 1 s, 5 s, 30 s
    growing delays, a few times
  3. 3
    Dead-letter queue
    parked for engineers to inspect, fix and replay
A message that keeps failing takes a detour instead of blocking everyone. · flow

Back pressure

If producers keep adding messages faster than consumers can ever process them, the queue grows without limit. Messages get old and useless (an OTP, or one-time password, that arrives 20 minutes late), and memory or disk fills up. You need to:

  • Monitor queue length and message age.
  • Auto-scale workers based on queue length (start more workers when the queue grows, and stop them when it shrinks).
  • If needed, push back on producers (slow them down, or reject non-essential work). This is called back pressure.

What should go on a queue?

Good fits:

  • Sending emails, SMS and push notifications.
  • Image and video processing (resizing, transcoding: converting video to other formats and sizes).
  • Generating reports, invoices and PDFs.
  • Syncing data to search indexes, analytics and data warehouses (large databases built for reports).
  • Calling slow or unreliable third-party APIs.
  • Anything the user doesn't need to see immediately.

Poor fits:

  • Anything the user must see the result of right now: login checks, "is this username available?", getting a price.
  • Simple, fast work where a queue only adds complexity.
ToolWhat it's like
RabbitMQA classic, flexible message broker (a server that receives and routes messages) with rich routing options
Amazon SQSA fully managed queue on AWS; very simple, scales automatically
Google Cloud Pub/Sub, Azure Service BusManaged messaging on other clouds
Redis (lists, Streams)Lightweight queues, often used by job libraries like BullMQ and Sidekiq
Apache KafkaNot a classic queue but a distributed log (an append-only list of messages spread over many servers): messages are kept for days and can be re-read. Very high throughput, which means it handles a very large number of messages per second (Part 6)

How the user sees async results

If work happens later, how does the user find out?

  • Just tell them: "We'll email your invoice shortly."
  • Polling: the app asks again and again. It checks GET /jobs/123 every few seconds ("Processing… Done!").
  • Push: the server sends the news to the client. It uses WebSockets (a connection that stays open so the server can send data at any time) or push notifications when the job finishes (Part 5).
  • Webhooks: for other systems, "we'll call your URL when it's done". A webhook is a web address that you give us, and we send a request to it when something happens.

Trade-offs

  • ✅ Faster responses, decoupled services, resilience to downstream failures, and smoother load.
  • ❌ Eventual results. Work happens "soon", not "now". The UX (user experience) has to handle this.
  • ❌ Duplicates and ordering. Messages may be delivered more than once or out of order, so consumers must handle both.
  • ❌ More moving parts: the queue itself must be monitored, scaled and kept highly available.
  • ❌ Harder debugging. A single user action is now spread across several services and time. Good logging and tracing (Part 8) become essential.

When not to use a queue: small apps where the "slow" work takes 50 ms and rarely fails, or anything where the user needs the result before continuing.

In the Real World

Amazon SQS is one of the oldest AWS services. It was publicly announced in 2004, before S3 and EC2. That says a lot about how central queues were to how Amazon built its own systems: services talk through queues so a problem in one doesn't bring down the others. (S3 is Amazon's file storage service and EC2 is its virtual server service.)

Food delivery and ride-hailing apps. When you place an order or book a ride, the core action is confirmed quickly. Notifications to the restaurant or driver, receipt emails, analytics events and promotions are typically handled through queues and event streams behind the scenes.

Video platforms. When you upload a video to a platform like YouTube, you get an "upload complete" message, then "processing…". The upload goes into storage, and a job queue sends it to workers that transcode it into many resolutions. That's why HD versions sometimes appear a little later than SD.

Banking OTPs and alerts. When a bank sends you a transaction alert SMS, it's usually produced by a queue-based system. It's a nice example of why monitoring message age matters: an OTP that arrives 10 minutes late is useless.

Flash sales. E-commerce sites use queues to absorb bursts of orders, and sometimes even put users into a "virtual waiting room" queue before they can reach checkout, to protect their systems during huge sales.

Interview Questions

Q1Why put a message queue between services?

To return fast (the user waits only for essential work), decouple producers from consumers (add new consumers without touching the producer), survive downstream outages (messages wait instead of failing), and smooth spikes (workers drain the queue at a safe, steady rate).

Q2How does a queue avoid losing a message when a worker crashes?

The worker must acknowledge a message after processing. Until then it's only hidden (SQS visibility timeout) or held as unacked (RabbitMQ); if no ack arrives, it's redelivered to another worker. The side-effect is at-least-once delivery.

Q3Why must consumers be idempotent?

Because redelivery means a message can be processed twice — say the email was sent but the worker crashed before acking. Record what was done (a processed-message ID, or a unique constraint on order + email type) and skip duplicates.

Q4What metrics would you watch on a queue?

Queue depth, the age of the oldest message, consumer throughput, error and retry rates, and dead-letter-queue size. Age matters most for time-sensitive work: an OTP that arrives 10 minutes late is useless.

Q5What would you not put on a queue?

Anything the user needs before continuing — login checks, username availability, a price quote — and trivial fast work where a queue only adds latency, duplicates and operational overhead.

Key Takeaways

  • A message queue lets producers hand off work to consumers asynchronously, like a kitchen ticket rail.
  • Queues give you fast responses, decoupling, resilience and load levelling, and you scale by adding workers.
  • Acknowledgements prevent message loss, but mean a message can be processed more than once, so make consumers idempotent.
  • Use retries and dead-letter queues for failing messages, and monitor queue length and message age.
  • Queue work that the user doesn't need immediately. Keep "must know now" work synchronous.

Further Reading

  • The System Design Primer on GitHub (section "Asynchronism": message queues, task queues and back pressure)
  • The official RabbitMQ tutorials
  • The Amazon SQS Developer Guide
  • Azure Architecture Center patterns "Queue-Based Load Leveling" and "Competing Consumers"

Published by Jainik · Last updated · How these lessons are made

Keep learning

Part of the study guide

Related series