RabbitMQ — Common Interview Questions and Answers
1. What is RabbitMQ and when do you use it?
RabbitMQ is a message broker implementing AMQP 0.9.1 (with extensions). It accepts messages from producers, routes them through exchanges to queues, and delivers to consumers.
Use it for:
- Work queues (background jobs, async processing).
- Pub/sub with multiple consumers.
- RPC-style request/response.
- Decoupling services with complex routing needs.
It’s not optimized for:
- Very high throughput streaming (Kafka wins).
- Long-term log retention with replay (Kafka wins).
2. Producer → Exchange → Queue → Consumer — explain.
Producers don’t send to queues directly; they publish to exchanges with a routing key. The exchange routes to bound queues based on its type:
- direct — exact routing key match.
- topic — pattern match (
orders.*.placed,#.failed). - fanout — broadcast to all bound queues.
- headers — match by header values.
A queue gets messages only if it’s bound to an exchange with a binding rule that matches the routing key.
3. What’s an exchange binding?
A binding is a routing rule attaching a queue to an exchange.
channel.queue_bind(exchange="orders", queue="email", routing_key="order.placed")
The exchange consults bindings on each publish: “for this routing key, which queues should I deliver to?” One queue can be bound to multiple exchanges; one exchange can have many queues bound.
4. Classic vs Quorum vs Stream queues?
| Classic | Quorum | Stream | |
|---|---|---|---|
| Replication | mirroring (deprecated) | Raft-based, strong consistency | replicated log |
| Use case | (deprecated) | work queues, durable messaging | event streaming, fan-out |
| Reads | once per consumer | once per consumer | replayable by offset |
| Tooling | mature but legacy | modern default | newer, less mature |
For new deployments: quorum queues for tasks, streams for streaming. Avoid classic mirrored queues.
5. How do you make messages durable?
Three things, all required:
- Durable queue —
durable=Trueonqueue_declare. Queue survives broker restart. - Persistent messages —
properties=BasicProperties(delivery_mode=2). Messages written to disk. - Publisher confirms —
channel.confirm_delivery()+ check return value. Broker acknowledges receipt.
Even with all three, you have at-least-once delivery, not exactly-once. Consumer must be idempotent.
6. What’s a DLX and when do you use it?
Dead-Letter Exchange — set x-dead-letter-exchange on a queue; rejected/expired/over-limit messages route there.
channel.queue_declare(queue="orders", arguments={
"x-dead-letter-exchange": "dlx",
"x-dead-letter-routing-key": "failed-orders",
})
Use cases:
- Poison messages that fail every retry → DLX → DLQ → human investigation.
- Time-bounded retries (TTL on retry queue → DLX back to main queue).
- Failed Celery tasks (Celery + RabbitMQ + DLX pattern).
7. What’s the prefetch count and how do you tune it?
basic.qos(prefetch_count=N) — how many unacked messages the broker pushes to a consumer at once.
- Too low (1): every message requires a round-trip; throughput suffers.
- Too high (1000): one slow consumer hogs a thousand messages; the rest sit idle.
- Typical: 10-100 for short work, 1 for long-running tasks.
For Celery: worker_prefetch_multiplier defaults to 4 × worker_concurrency. Lower it for slow tasks to avoid starvation.
8. What’s the difference between basic.ack, basic.nack, and basic.reject?
ack— message processed successfully; broker can delete it.nack— negative ack withmultipleandrequeueoptions. Withrequeue=False, message is dead-lettered (or dropped if no DLX).nackcan batch (multiple=True).reject— older API, single message,requeueoption.
Default Celery behavior: ack on success, nack with requeue=False on hard failure → DLX picks it up.
9. RabbitMQ vs Kafka — when each?
| RabbitMQ | Kafka | |
|---|---|---|
| Mental model | broker pushes, consumer acks | consumer pulls from log |
| Throughput | ~50k/s per node | ~1M+/s per cluster |
| Routing flexibility | high (exchanges) | low (partition by key) |
| Replay | message acked = gone | offsets within retention; replayable |
| Best for | work queues, RPC, complex routing | event streaming, audit logs, log integration |
Most companies end up running both: RabbitMQ for Celery tasks, Kafka for cross-service event streaming.
10. How does RabbitMQ achieve HA?
Quorum queues replicate across N nodes via Raft consensus. A queue with N=5 tolerates 2 node failures. Writes go to the leader; replicated to a majority of followers before ack.
For broker discovery: a cluster of RabbitMQ nodes federates via Erlang networking. Clients connect via a load balancer that distributes across nodes (or use rabbitmq-uri with multiple hosts).
Classic mirrored queues (deprecated) replicated to designated followers; less reliable failover semantics than quorum.
11. What’s a publisher confirm?
By default basic.publish doesn’t return anything — fire-and-forget. The broker may not have actually persisted the message.
channel.confirm_delivery()
ok = channel.basic_publish(
exchange="...", routing_key="...", body=...,
mandatory=True,
)
if not ok:
# broker didn't confirm; resend or alert
...
Confirms add latency (broker waits to confirm) but guarantee durability. For mission-critical work, always enable confirms.
12. What’s the mandatory flag?
mandatory=True on publish: if no queue is bound to the exchange with a matching routing key, the message is returned to the publisher (not silently dropped).
Useful for catching routing-key typos and dead consumers. The Channel must have a basic_return callback registered to receive the bounce.
13. Why are channels not threads-safe?
The AMQP wire protocol is multiplexed across one TCP connection via channels — each channel is a logical session. A single channel processes commands serially; concurrent thread access garbles the stream.
Pattern: one connection per app, one channel per thread/coroutine. Pools manage this.
14. What’s a lazy queue?
A queue that prefers disk over RAM. Default queues cache messages in RAM and only write to disk for durability; under deep backlog, the broker OOMs.
channel.queue_declare(queue="bulk", arguments={"x-queue-mode": "lazy"})
Use for queues that may accumulate large backlogs of large messages. Slightly slower per-message but won’t blow up the broker.
In modern RabbitMQ (3.12+), quorum queues have similar storage semantics and lazy queues are less common.
15. How do you handle a poison message?
A poison message fails every attempt — malformed payload, missing reference, etc.
Pattern:
- Consumer fails,
nackwithrequeue=False. - Configured DLX routes the message to a DLQ.
- Alert on DLQ depth growth.
- Human inspects: fix the bug, or delete the message if unfixable.
- Replay the DLQ (shovel back to main queue) once fixed.
Without DLX, a poison message either bounces forever (if requeue=True) or vanishes silently. Always configure DLX for production queues.
16. What’s RabbitMQ’s “shovel”?
A built-in plugin that copies messages from one queue/exchange to another — possibly on a different broker. Used for:
- Cross-data-center replication of specific queues.
- Replaying a DLQ back to the main queue.
- Migrating queues between brokers.
rabbitmq-plugins enable rabbitmq_shovel
Configure via management UI or rabbitmq.conf.
17. RabbitMQ vs Celery — what’s the relationship?
Celery is a task queue library; RabbitMQ is one of its supported brokers (Redis, SQS, others also work). Celery uses RabbitMQ exchanges + queues internally:
- Each task queue is a RabbitMQ queue.
- Routing keys distinguish task names.
- DLX configuration on the Celery queue enables the DLQ pattern.
Many “Celery with RabbitMQ” production issues are actually RabbitMQ tuning issues — prefetch, durability, DLX.
18. Can you do RPC with RabbitMQ?
Yes — the classic AMQP RPC pattern:
- Client declares an exclusive temporary reply queue.
- Client sends request with
reply_to(the temp queue) and a correlation ID. - Worker processes, publishes reply to the
reply_toqueue with the correlation ID. - Client consumes from the temp queue, matches by correlation ID.
result = channel.queue_declare(queue="", exclusive=True)
reply_queue = result.method.queue
corr_id = str(uuid.uuid4())
channel.basic_publish(
exchange="", routing_key="rpc_queue",
properties=pika.BasicProperties(reply_to=reply_queue, correlation_id=corr_id),
body=request,
)
# Consume from reply_queue, match correlation_id
Rarely the right tool — HTTP/gRPC is usually clearer. Use RabbitMQ RPC when you want broker-mediated load distribution + queue-based backpressure.
19. How does message TTL work?
Two scopes:
- Per-queue:
x-message-ttlon queue declare. Every message expires after N ms. - Per-message:
expirationon publish, as a string of ms.
Expired messages are dropped (or dead-lettered if DLX configured). Useful for:
- Cache invalidations that go stale.
- Time-sensitive notifications.
- Retry-with-delay patterns (TTL on retry queue → DLX back to original).
20. Production tips?
- Always
durable=Trueanddelivery_mode=2for production. - Use quorum queues, not classic mirrored.
- Configure DLX on every operational queue.
- Set
basic.qos(prefetch_count=...)consciously per consumer. - Publisher confirms for important publishes.
- Monitor: queue depth, message rate, unacked count, connection count.
- Connection per app, channel per thread.
- Don’t rely on memory queues for large backlogs — quorum or lazy.
Mental model
| Concept | Quick definition |
|---|---|
| Connection | TCP connection to broker; per app |
| Channel | logical session on connection; per thread |
| Exchange | routes messages; producer publishes here |
| Queue | holds messages; consumer reads here |
| Binding | rule connecting exchange → queue |
| Routing key | string used by exchange to decide routing |
| DLX | exchange for dead-lettered messages |
| Prefetch | how many unacked messages per consumer |
| Quorum queue | Raft-replicated, modern HA |
| Stream | append-only log, like Kafka topic |
Interview angle
- “Explain exchanges and bindings.” - producers publish to an exchange, never directly to a queue. The exchange type decides routing: direct matches the routing key exactly, topic matches patterns, fanout copies to every bound queue, headers matches on attributes. Bindings connect exchanges to queues.
- “How do you avoid losing messages?” - durable exchanges and queues, persistent messages, publisher confirms on the producer side, and manual acknowledgement after processing on the consumer side. Missing any one of those reintroduces a loss window.
- “How do you handle a poison message?” - a dead-letter exchange after a bounded number of redeliveries, so one unprocessable message doesn’t block the queue or loop forever. Pair it with an alert on DLQ depth.
- “RabbitMQ or Kafka?” - RabbitMQ for routing flexibility, per-message acknowledgement and task distribution; Kafka for high-throughput ordered event streams with replay. They solve different problems despite overlapping.