backend / message queues / rabbitmq / 04_rabbitmq_interview.md

RabbitMQ — Common Interview Questions and Answers

4 interview angles 8 min read source

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:

  1. Durable queuedurable=True on queue_declare. Queue survives broker restart.
  2. Persistent messagesproperties=BasicProperties(delivery_mode=2). Messages written to disk.
  3. Publisher confirmschannel.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 with multiple and requeue options. With requeue=False, message is dead-lettered (or dropped if no DLX). nack can batch (multiple=True).
  • reject — older API, single message, requeue option.

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:

  1. Consumer fails, nack with requeue=False.
  2. Configured DLX routes the message to a DLQ.
  3. Alert on DLQ depth growth.
  4. Human inspects: fix the bug, or delete the message if unfixable.
  5. 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:

  1. Client declares an exclusive temporary reply queue.
  2. Client sends request with reply_to (the temp queue) and a correlation ID.
  3. Worker processes, publishes reply to the reply_to queue with the correlation ID.
  4. 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-ttl on queue declare. Every message expires after N ms.
  • Per-message: expiration on 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=True and delivery_mode=2 for 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.