RabbitMQ: Exchanges, Queues, Acks & Dead Letters

Lab
Easy
Medium
Hard

Topic exchange (wildcards)Medium

* matches one word, # matches zero or more. Great for event buses.

Topology

exchange “events” (topic)
binding: “order.#” → orders-q
binding: “*.created” → created-q
binding: “#” → audit-q
consumer order-svc ← orders-q
consumer welcome-svc ← created-q
consumer audit-svc ← audit-q

Experiment

Consumer prefetch (unacked messages allowed)
Publish one more messagerouting key

Adding a message with a different key shows how this exchange type routes it (or drops it).

1 / 150 acked
Overview
Topology declared. A producer never writes to a queue directly: it publishes to an exchange (topic). Bindings tell the exchange which queues get which messages. Consumers read from queues.

Producer → exchange → queues → consumers

message redelivered
EXCHANGEeventstopicorder.#*.created#QUEUE · orders-q0 readyQUEUE · created-q0 readyQUEUE · audit-q0 readyorder-svcprefetch 1 · acked 0unacked:welcome-svcprefetch 1 · acked 0unacked:audit-svcprefetch 1 · acked 0unacked:
Routing rule
* = one word · # = zero or more
Acknowledgement
A message is only deleted after the consumer acks it
Delivery guarantee
At-least-once: crashes cause redelivery, so handlers must be idempotent
✓ Everything was consumed

* matches one word, # matches zero or more. Great for event buses. Route check for the next key you add (“order.created”): orders-q, created-q, audit-q.

Learn the theory

RabbitMQ: Exchanges, Queues, Acks & Dead Letters: how it works

4 min read · Updated

RabbitMQ is a message broker: applications send messages to it, and other applications receive them, so the two sides don't have to be online, fast or written in the same language at the same time. Its distinctive idea is that producers never write directly to queues. They publish to an exchange, and bindings decide which queues receive a copy. Consumers read from queues and acknowledge each message; anything unacknowledged can be redelivered. Understanding those four words — exchange, binding, queue, ack — explains almost everything RabbitMQ does.

In short

  • Producers publish to an exchange; bindings route messages to queues; consumers read from queues.
  • Exchange types: direct (exact key), topic (wildcards * and #), fanout (broadcast), headers.
  • A message is deleted only after the consumer acks it. If the consumer dies, it's redelivered — at-least-once delivery.
  • Prefetch limits unacked messages per consumer. Too high and a slow consumer hoards work; 1 gives fair dispatch.
  • Failed messages should be rejected without requeue into a dead-letter exchange/queue, not requeued forever.

The AMQP model

  1. A producer publishes a message with a routing key to an exchange.
  2. The exchange applies its routing rules using bindings (queue + binding key).
  3. Matching queues each get a copy and hold it until it's consumed.
  4. A consumer receives messages from a queue and sends an ack when it's done.

Separating the exchange from the queue is what makes RabbitMQ flexible: you can add a new consumer (a new queue + binding) without touching the producer.

Exchange types

TypeRouting ruleTypical use
directDelivers to queues whose binding key equals the routing key.Targeted routing: error → alert queue.
topicPattern match on dot-separated words: * = exactly one word, # = zero or more.Event buses: order.created, *.created, order.#.
fanoutIgnores the key; copies to every bound queue.Broadcast / publish-subscribe.
headersMatches on message headers instead of the key.Rare; complex attribute routing.
default (nameless)Routes to the queue whose name equals the routing key.Simple work queues.

Competing consumers and prefetch

Several consumers on the same queue compete: each message goes to exactly one of them, which spreads work. Each consumer can hold up to prefetch unacknowledged messages.

  • prefetch = 1: a consumer only receives a new message after acking the last one — fair dispatch; a slow worker doesn't hoard.
  • Large prefetch: higher throughput for tiny messages, but a slow consumer can sit on many messages while a fast one idles (the lab's “prefetch = 10” preset).

Acknowledgements and redelivery

With manual acks, RabbitMQ keeps a delivered message in the queue marked unacked until the consumer confirms. If the consumer's connection closes without acking, the broker requeues the message (redelivered=true) for another consumer.

  • ack: done — delete the message.
  • nack/reject with requeue=true: put it back at the head of the queue.
  • nack/reject with requeue=false: discard it, or dead-letter it if a DLX is configured.

Dead-letter exchanges and poison messages

A message that always fails (a poison message) will loop forever if you keep requeueing it, burning CPU and blocking the head of the queue. Instead, configure a dead-letter exchange (DLX) on the queue. Messages that are rejected without requeue, expire (TTL) or overflow a max-length are routed to a dead-letter queue where you can inspect, alert and replay them.

await ch.assertQueue('tasks', {
  arguments: {
    'x-dead-letter-exchange': 'dlx',
    'x-dead-letter-routing-key': 'tasks.dead',
  },
});
// in the consumer on permanent failure:
ch.nack(msg, false, false); // requeue = false → goes to the DLQ

Making it reliable

  • Declare queues and exchanges durable and publish persistent messages to survive broker restarts.
  • Use publisher confirms so the producer knows the broker accepted a message.
  • Use quorum queues for replicated, highly available queues.
  • Cap retries with a retry count header or delayed-retry queues, then dead-letter.
  • Monitor queue length, unacked count and consumer utilization.

RabbitMQ vs BullMQ vs Kafka

RabbitMQBullMQKafka
ModelSmart broker, routing, per-message acksRedis-backed job queueDistributed append-only log
Best forRouting and messaging between servicesBackground jobs in Node.jsHigh-volume event streaming, replay
Message after consumeDeleted on ackKept briefly, then removedRetained; consumers track offsets
LanguagesMany (AMQP)Node.js (and Python/Elixir clients)Many

Frequently asked questions

What is an exchange in RabbitMQ?
A routing component that receives messages from producers and distributes them to queues according to its type (direct, topic, fanout or headers) and the bindings between it and queues.
What is the difference between direct, topic and fanout exchanges?
Direct matches the routing key exactly, topic matches wildcard patterns (* for one word, # for zero or more), and fanout ignores the key and copies the message to all bound queues.
What does prefetch do in RabbitMQ?
It limits how many unacknowledged messages the broker will deliver to a consumer at once. A prefetch of 1 gives fair dispatch among competing consumers; a high value increases throughput but can starve other consumers.
What happens if a RabbitMQ consumer crashes before acking?
The broker detects the closed connection and requeues the unacked messages (marked redelivered) so another consumer can process them. This gives at-least-once delivery.
What is a dead-letter queue?
A queue that receives messages which were rejected without requeue, expired or exceeded a queue limit, via a dead-letter exchange. It lets you inspect and replay failed messages instead of losing them or looping.
Does RabbitMQ guarantee message ordering?
Messages are ordered within a single queue for a single consumer, but ordering is not guaranteed with multiple consumers, redeliveries or requeues.

Keep exploring

More interactive labs

All labs

Job Queues with BullMQ (Redis)

Watch jobs move through waiting, active, delayed, completed and failed — and see what BullMQ stores in Redis.

Queues & Jobs14 min · Intermediate

Webhooks: Delivery, Retries & Signatures

Why you get duplicate deliveries, lost events and forged requests — and how to stop them.

Queues & Jobs12 min · Intermediate

JS Event Loop Visualizer

See the call stack, microtasks and timers run, frame by frame.

JavaScript10 min · Intermediate

CORS & Preflight Simulator

Edit the request and the server headers, then see exactly why the browser blocks it.

Networking12 min · Intermediate

HTTP Caching Explained

Cache-Control, ETag and 304s, one request at a time.

Networking12 min · Intermediate

What Happens When You Type a URL

DNS, TCP, TLS and HTTP — with a stopwatch.

Networking10 min · Beginner

How Database Indexes Work (B-Tree)

See a B-tree lookup beat a full table scan, and why some queries ignore your index.

Databases12 min · Intermediate

What Happens When 100 Reads Hit the Database

Connection pools, CPU workers, caches and replicas, as 100 requests arrive at once.

Databases12 min · Intermediate

SQL Isolation Levels Lab

Run two transactions side by side and watch dirty reads, phantoms and write skew appear.

Databases15 min · Advanced

Sharding & Replication

Split data across servers, copy it for reads, and see what each choice costs.

Databases15 min · Advanced

Redis Commands & Data Structures

Strings, lists, hashes, sets and sorted sets, one command at a time.

Redis15 min · Beginner

Redis Caching: Cache-Aside, TTL & Eviction

Hit or miss? Watch LRU, LFU, TTLs and a cache stampede decide your database load.

Redis12 min · Intermediate

Cron Jobs: Schedules, Overlaps & Missed Runs

Write a cron expression and watch what really happens when jobs overlap or the scheduler is down.

Queues & Jobs10 min · Beginner

Git Commands, HEAD & Refs (Visual)

Every core git command drawn with arrows: three areas, commits, branches, HEAD and remotes.

Git15 min · Beginner

Git Merge vs Rebase

Watch the commit graph change under merge, rebase and squash.

Git10 min · Beginner