DEV Community

Cover image for Message queues and pub/sub for system design interviews, explained
Gowtham Potureddi
Gowtham Potureddi

Posted on

Message queues and pub/sub for system design interviews, explained

Message queues and pub/sub for system design interviews, explained

Somewhere in almost every system design interview you reach a step that should not block the user: send the email, transcode the video, update the search index, settle the payment. The instant you say "we'll do that asynchronously," you have reached for a message queue — and that is where the real questions start. Messaging is not one idea; it is a set of deliberate choices about who sends, who receives, how many times a message can be delivered, and what happens when a consumer is slow, crashes, or sees the same message twice. "Put it on a queue" is the easy half of the sentence; everything true and hard lives in the delivery guarantee.

The reason this comes up in almost every round is that a queue buys two properties every design cares about: decoupling (the producer does not wait for, or even know about, the consumer) and resilience to load (a spike is absorbed by the queue instead of crushing a downstream service). But a queue is also a new piece of stateful infrastructure in the middle of your system, and the moment a message can be retried, you own a new set of problems — duplicates, out-of-order processing, a consumer that cannot keep up, and messages that can never be processed at all. This guide walks the choices a strong candidate names out loud: queue vs pub/sub, the delivery guarantees (at-most-once, at-least-once, exactly-once), and the hard parts — ordering, idempotency, backpressure, consumer groups and the dead-letter queue — that the follow-ups are really about.

Title card for a system design interview explainer on message queues and pub/sub, showing a producer sending messages into a broker that fans them out to multiple consumers, with one path labelled point-to-point queue and another labelled publish/subscribe topic, and tags for at-least-once delivery, ordering and a dead-letter queue, on a dark citrus-and-lavender background.

Why message queues come up in system design interviews

Messaging rarely gets its own whiteboard prompt — it shows up inside a design the moment you separate "accept the request" from "do the slow work." A user uploads a video and you return immediately while transcoding happens later; an order is placed and a dozen downstream services need to know. You draw a queue between the producer and the workers, and the interviewer leans in: not whether to use a queue, but how it behaves. What happens if a worker dies mid-message? Can the same message be processed twice? Does order matter, and if so, how do you keep it? A queue is a favourite probe precisely because the box is trivial to draw and the correctness of the whole system hinges on the guarantees behind it.

The interviewer rarely lets "we'll put it on a queue" stand on its own. "A payment-processing worker pulls a 'charge card' message off the queue, charges the customer, and then crashes before it can acknowledge the message. The queue, having never seen an ack, redelivers that same message to another worker. Walk me through exactly what happens to the customer, and what you'd build so they aren't charged twice." That question — duplicate delivery and idempotency — is the one a memorised "use Kafka" answer can never survive, and the one this topic is really testing.

The rounds on Zynter are modelled on how the loop runs, not scraped from any candidate's recall. What matters for prep is the shape: you name a messaging choice, and the next sentence attacks its weakest point — a lost message, a duplicate, an out-of-order write, or a consumer that has fallen hopelessly behind.

What the interviewer is really testing

A messaging answer is read on the same four dimensions as any design decision. Problem solving — did you pick a delivery guarantee that fits the work (a dropped metric is fine; a dropped payment is not), or reach for "exactly-once" because it sounds safest? Communication — can you say in one sentence why at-least-once plus idempotency is the usual real-world answer? Technical execution — do you handle duplicates, ordering and the poison message, or did you assume every message is processed cleanly exactly once? Time management — did you spend your words on the guarantee and failure story, not on reciting that "Kafka is distributed"? Reading this guide teaches the mechanisms; it does not teach you to defend one while someone pushes on the double charge.

Queue vs pub/sub: point-to-point or broadcast?

The first distinction to name is the messaging model, because it decides who receives a message.

Point-to-point (a queue) — a producer puts a message on a queue and exactly one consumer processes it. If you run many workers for throughput, they compete for messages and each message goes to one of them — this is the competing consumers pattern, and it is how you scale work horizontally. Use it for tasks: resize this image, send this email, charge this card — each unit of work should happen once.

Publish/subscribe (a topic) — a producer publishes a message to a topic and every subscriber gets its own copy. One "order placed" event fans out to the email service, the analytics service, the inventory service and the recommendation service, each reacting independently. Use it for events: something happened, and an unknown and growing set of consumers cares.

The distinction that reads as experience: a queue delivers each message to one worker so a task happens once; pub/sub delivers each message to every subscriber so one event can drive many reactions — and real systems like Kafka blend the two, where a topic is pub/sub across consumer groups but a queue (competing consumers) within a group. Naming that blend, rather than treating queue and pub/sub as rival products, is what separates a real answer.

A diagram contrasting a point-to-point queue with publish/subscribe, showing on one side a producer putting messages on a queue where several competing consumers each take a different message so each is processed once, and on the other side a producer publishing to a topic where every subscriber receives its own copy of each message, illustrating one-consumer tasks versus broadcast events.

Delivery guarantees: at-most-once, at-least-once, exactly-once

This is the heart of a messaging interview, and the follow-up from the opening scenario lands here. Every messaging system makes a promise about how many times a message can be delivered, and that promise shapes how the consumer must be written.

At-most-once — the message is delivered zero or one times; it may be lost, but never duplicated. The producer fires and forgets, and the consumer acknowledges before processing (or there is no ack at all). Fastest and simplest, and acceptable when loss is tolerable — live metrics, non-critical notifications, real-time comments where an occasional drop is invisible.

At-least-once — the message is delivered one or more times; it is never lost, but can be duplicated. The consumer acknowledges only after it has finished processing, so a crash before the ack causes a redelivery. This is the default for most real systems because losing a message is usually worse than seeing it twice — and the duplicate is a problem you can solve.

Exactly-once — the message is effectively processed once: never lost, never duplicated in its effect. It is the guarantee everyone wants and the one to be careful about, because true end-to-end exactly-once is expensive and often impossible across arbitrary external systems (you cannot un-send an email). What modern systems actually offer is exactly-once within the system's own boundary (Kafka's transactions across its topics), and what you build at the edge is at-least-once delivery plus an idempotent consumer so a duplicate has no additional effect.

Guarantee Can lose? Can duplicate? Consumer acks Best for
At-most-once yes no before processing metrics, non-critical notifications — loss tolerable
At-least-once no yes after processing the real-world default — pair with idempotency
Exactly-once no no (in effect) transactional / dedup hard & costly end-to-end; usually "at-least-once + idempotent" in practice

The sentence that scores: I'd use at-least-once delivery and make the consumer idempotent — a dedup key or an upsert keyed on the message's ID — so redelivery after a crash is harmless; I'd only chase true exactly-once inside a single system that supports transactions, and never claim it across an external side effect like charging a card or sending an email without an idempotency key.

A diagram of the three message-delivery guarantees, showing at-most-once where a message can be lost but never duplicated, at-least-once where a message is never lost but a crash before acknowledgement causes a redelivery and possible duplicate, and exactly-once shown as at-least-once delivery paired with an idempotent consumer that deduplicates by message id so a duplicate has no additional effect.

System Design
HLD · Core concept
Pick and defend a delivery guarantee out loud

Practise on Zynter →

The hard parts: ordering, backpressure, consumer groups and dead letters

Once delivery is settled, the follow-ups move to the four problems that separate a diagram from a working system.

  • Ordering — many systems guarantee order only within a partition, not across the whole topic. The standard answer is to partition by a key (all events for one user, or one order, go to the same partition and are processed in order) and accept that across partitions there is no global order. If you need strict total order you give up parallelism — a trade-off to state, not wish away.
  • Idempotency — the direct consequence of at-least-once. Processing the same message twice must produce the same result as processing it once: dedup on a message ID you record, or make the operation naturally idempotent (an upsert, a "set balance to X" rather than "add X"). This is how you make redelivery safe, and it is the single most important follow-up to be ready for.
  • Backpressure — what happens when producers outrun consumers. The queue grows; if unbounded it exhausts memory or disk, if bounded the producer must slow down, block or shed load. The answers to name: scale consumers out (competing consumers), bound the queue and apply flow control, or drop/sample low-value messages. A queue smooths a spike; it does not fix a consumer that is permanently too slow.
  • Consumer groups — how you scale consumption while controlling delivery: within a group, each partition is read by one consumer so work is divided and each message is handled once; across groups, every group gets the full stream (this is the pub/sub layer). Adding consumers up to the partition count increases throughput; beyond that, extra consumers sit idle.
  • Dead-letter queue (DLQ) — the escape hatch for a poison message that fails every time (malformed, or a bug it keeps tripping). After N failed retries, move it to a DLQ so it stops blocking the queue and can be inspected and replayed later. Without a DLQ, one bad message can wedge a whole partition behind endless retries.

The point to state plainly: partitioning buys ordering where it matters, idempotency makes redelivery safe, backpressure keeps a spike from becoming an outage, and a dead-letter queue keeps one poison message from becoming a stuck pipeline. Those are four different jobs, and a strong answer names which one each mechanism does.

A diagram of the hard parts of message queues, showing a topic split into partitions where messages with the same key stay ordered within a partition, an idempotent consumer deduplicating a redelivered message by id, a backpressure control slowing a producer when a bounded queue fills, a consumer group dividing partitions among its consumers, and a dead-letter queue catching a poison message after repeated failures.

System Design
HLD · Distributed Message Queue
Defend your ordering and idempotency plan out loud

Practise on Zynter →

Which broker, and when: a quick orientation

"Use a message queue" is ambiguous until you can say roughly which kind, because the common brokers make different trade-offs:

  • Log-based (Kafka, and similar) — an append-only, partitioned, replayable log. Consumers track their own offset and can re-read history, retention is time- or size-based, and throughput is very high. The default when you need a durable event stream, replay, high throughput, and pub/sub with consumer groups.
  • Traditional broker (RabbitMQ and similar) — rich routing (exchanges, bindings), per-message acknowledgement, and messages that are typically gone once acknowledged. The fit when you want flexible routing and classic task-queue semantics rather than a replayable log.
  • Managed cloud queues (SQS and similar) — a simple, fully managed queue with at-least-once delivery and an optional FIFO mode; little to operate, which is often the right call in an interview when messaging is a supporting detail rather than the whole problem.

You will not be marked on memorising feature matrices. The signal is matching the shape — "I need a replayable, high-throughput event log, so something Kafka-like" versus "I need a simple managed task queue, so something SQS-like" — and then returning to the guarantees and the failure story, which is where the points actually are.

How to talk about messaging in an interview

The probes from every section rhyme. Master these five and you can defend a messaging decision in any design, not just this checklist.

  • "Queue or pub/sub here?" → A queue (competing consumers) when each task must happen once; pub/sub (a topic) when one event must fan out to many independent consumers — and a Kafka-style topic gives me both, pub/sub across groups and a queue within a group. (problem solving)
  • "Which delivery guarantee?" → At-least-once as the default because losing a message is usually worse than a duplicate, paired with an idempotent consumer; at-most-once only where loss is tolerable; true exactly-once only inside a system that supports transactions. (technical execution)
  • "A worker crashes after doing the work but before acking — what happens?" → The message is redelivered (at-least-once), so I rely on idempotency — a dedup key or an upsert — to make the second processing a no-op; the customer isn't charged twice. (technical execution)
  • "Does ordering matter, and how do you keep it?" → Partition by a key so all events for one entity stay ordered within a partition; accept no global order across partitions, and only force total order when I'm willing to give up parallelism. (communication)
  • "Producers are outrunning consumers — what happens to the queue?" → Backpressure: scale consumers out up to the partition count, bound the queue with flow control, shed or sample low-value messages, and send repeatedly failing messages to a dead-letter queue so one poison message can't wedge the pipeline. (problem solving)

Each maps to a debrief dimension. Reading them teaches the answers; it does not teach you to deliver them while someone interrupts with "but now the same message arrived three times" — that only comes from saying them out loud.

How to prepare: a short plan

You do not need a new topic for every design. You need the messaging vocabulary automatic, so when a design needs to decouple work you reach for the right model and guarantee without thinking.

  • Day 1: Learn queue vs pub/sub and the three delivery guarantees until you can state each one's one-sentence trade-off and the kind of work it fits from memory.
  • Day 2: Drill the hard parts — ordering via partitioning, idempotency for at-least-once, backpressure, consumer groups and the dead-letter queue. These are where the follow-ups live.
  • Day 3: Place a queue inside a real design (a video upload pipeline, an order system, a notification fan-out): pick a model and a guarantee, make the consumer idempotent, handle ordering and the poison message. Then do it out loud, timed, with someone pushing on the double charge and the slow consumer.

This explainer feeds the broader Top 25 system design interview questions — asynchronous messaging is a decision inside most of them, and designing the queue itself is its own HLD round, so getting this automatic pays off across the whole list.

Frequently asked questions

What is the difference between a message queue and pub/sub?

A message queue is point-to-point: a producer puts a message on the queue and exactly one consumer processes it, which makes it ideal for distributing tasks across competing workers so each unit of work happens once. Publish/subscribe is broadcast: a producer publishes to a topic and every subscriber receives its own copy of each message, which is ideal for events that many independent services need to react to. The two are not rival products so much as different delivery semantics — modern log-based systems like Kafka blend them, so a topic behaves as pub/sub across consumer groups while behaving as a competing-consumers queue within a single group. The interview-worthy framing is "a queue delivers each message to one worker; pub/sub delivers each message to every subscriber."

What are at-most-once, at-least-once and exactly-once delivery?

They are the three promises a messaging system can make about how many times a message is delivered. At-most-once means a message may be lost but never duplicated — the consumer acknowledges before processing — which suits data where occasional loss is acceptable, like live metrics. At-least-once means a message is never lost but may be delivered more than once, because the consumer acknowledges only after processing and a crash before the ack triggers a redelivery; this is the default for most real systems. Exactly-once means the message is effectively processed once with no loss and no duplicate effect, which is genuinely hard and often impossible end-to-end across external side effects, so in practice it is achieved as at-least-once delivery paired with an idempotent consumer, or via transactions within a single system's boundary.

Why is idempotency important in messaging?

Because the real-world default is at-least-once delivery, which means a consumer must expect to see the same message more than once — most commonly when a worker finishes processing but crashes before acknowledging, so the broker redelivers the message to another worker. Idempotency is the property that processing the same message twice has the same effect as processing it once, which is what makes that redelivery safe. You achieve it by deduplicating on a unique message ID that you record, or by making the operation naturally idempotent, such as an upsert or setting a value to an absolute amount rather than incrementing it. For side effects like charging a card, you pass an idempotency key so the downstream system itself rejects the duplicate.

How do message queues preserve ordering?

Most distributed messaging systems do not guarantee a single global order across an entire topic, because that would force everything through one place and destroy parallelism. Instead they guarantee order within a partition: you choose a partition key — for example the user ID or order ID — so that all messages for the same entity land on the same partition and are processed in the order they were sent, while messages for different entities can be processed in parallel on different partitions. If a design genuinely needs strict total ordering across everything, you can use a single partition, but you must acknowledge that you are giving up parallelism to get it. The interview answer is to partition by the key whose ordering actually matters.

What is a dead-letter queue and why do you need one?

A dead-letter queue (DLQ) is a separate queue where messages are sent after they have failed processing a configured number of times. Its purpose is to handle a "poison message" — one that fails every time, perhaps because it is malformed or triggers a bug — which would otherwise be retried endlessly and block the consumer or wedge an entire partition behind it. By moving the message to a DLQ after N retries, the main queue keeps flowing, and the failed message can be inspected, fixed and replayed later rather than lost or stuck. A DLQ, combined with retries and backpressure handling, is part of a complete answer to "what happens when processing a message fails?"

How do I practise a system design interview out loud?

Reading about delivery guarantees is not the same as defending at-least-once-plus-idempotency while an interviewer pushes on a double charge. On Zynter, the HLD track runs live voice rounds modelled on how real system design loops run — including a Distributed Message Queue round — where you talk through a design on a shared canvas and a voice interviewer follows up one question at a time ("a worker crashed before acking — what happens?", "the same message just arrived twice"), then gives you a debrief on problem solving, communication, technical execution and time management. The score is a practice signal, not a hiring decision.

Practise it on Zynter

You can recite at-least-once and exactly-once. The round is won on defending the double charge out loud.

Pick an HLD system design round — including the Distributed Message Queue round — put a queue in your design on the canvas, and defend it out loud to a voice interviewer that keeps asking "which delivery guarantee, and why?" and "a worker crashed before it acked — what happens to that message?" — then read the debrief. Zynter.ai is on-demand mock interviews modelled on real company loops, with 150+ rounds across 20 companies including the HLD system design track (Sep 2026).

Practise an HLD system design round →
Browse all interview tracks

Top comments (0)