System Design · step by stepDesign a Message Queue
Step 1 / 9
▶  Watch it explained

Prefer a video walkthrough?

Design a Message Queue (like Kafka) — the walkthrough in full

A written version of the interactive walkthrough above — the same steps, decisions and trade-offs, laid out for reading, reference and search.

The big idea

What is a message queue?

One service produces events much faster than another can handle them — or you want many services to react to the same event without the producer knowing they exist. Calling each consumer directly is brittle: a slow or dead consumer drags the producer down with it.

Put a durable log in the middle. Producers append; consumers read at their own speed. The producer fires and forgets; consumers come and go, replay history, and never block the producer. This one primitive underpins event-driven systems everywhere.

How to read this: We build it one piece at a time — a problem, then the smallest fix. Watch the diagram grow into a Kafka-style cluster. Hit Begin.

Step 1 · The core

An append-only log

A producer needs to hand off a message reliably, and a consumer needs to read messages in order, possibly long after they were written. A delete-on-read queue forgets too soon and can’t support replay or multiple readers.

Design decision: Consumers read in order long after a write, and multiple readers want the same stream. Model the topic as…

The call: An append-only commit log with monotonic offsets. — Writes only append (sequential disk I/O — shockingly fast), nothing is deleted on read, and order is implicit in the offset. Any number of consumers read it independently.

Model the topic as an append-only commit log: writes only ever append to the end and get a monotonically increasing offset. Nothing is deleted on read. Sequential disk appends are shockingly fast, and order is implicit in the offset.

The log is the database: An ordered, immutable, replayable sequence is a deceptively powerful abstraction. Because reads don’t mutate it, any number of consumers can read the same log independently.

Step 2 · Beyond one disk

Partitions for parallelism

A single log is capped by one machine’s disk and CPU — both for write throughput and for how fast consumers can read. One ordered log can’t scale horizontally.

Design decision: One ordered log is capped by a single machine’s disk and CPU. How do you scale throughput?

The call: Split the topic into partitions, routed by key hash. — Each partition is its own log on a possibly different broker; hashing a key keeps related events ordered together while unrelated events spread out. Throughput scales with partition count.

Split the topic into partitions, each its own log on a possibly different broker. A Partitioner routes each message — usually by hashing a key — so the same key always lands in the same partition. Throughput scales with partition count.

Order is per-partition: Kafka guarantees order within a partition, not across them. Choosing a good key (user id, order id) keeps related events ordered while letting unrelated events spread across partitions for parallelism.

Step 3 · Many readers, no deletes

Consumer groups & offsets

If the log never deletes, how does a consumer know what it has and hasn’t read? And how do ten instances of a service share the work without each processing every message?

Design decision: The log never deletes. How do consumers track progress and share work without each reading everything?

The call: Consumer groups split partitions; each group stores an offset cursor. — Partitions are divided among a group’s members (processed once per group), and a server-side offset records position — so a crash resumes exactly where it left off and a rewind replays. Different groups read the same log independently.

Consumers join a group; the group’s partitions are divided among its members so each message is processed once per group. Each group tracks its position with an offset — a cursor stored by the broker. Different groups read the same log at totally different positions.

The cursor, not the data, moves: Reading is just advancing an offset. That makes replay trivial (rewind the offset) and lets independent teams consume the same stream without stepping on each other.

Step 4 · Don’t lose data

Replication

Partitions live on disks, and disks (and whole brokers) die. If a partition exists on only one broker, its death means permanent data loss and downtime for everyone reading or writing it.

Design decision: A partition lives on a disk, and disks die. How do you not lose committed data?

The call: Replicate each partition: a leader + in-sync replicas, ack after replication. — The leader takes reads/writes; in-sync replicas mirror it, and a write commits only once enough replicas have it (acks=all). Losing the leader loses nothing — an ISR is promoted.

Replicate each partition across several brokers. One is the leader (takes all reads/writes); the others are in-sync replicas that mirror it. A write is “committed” once enough replicas have it, so losing the leader loses nothing.

Leader + ISR: The in-sync replica set is the safety margin. Acknowledge writes only after they’re replicated (acks=all) to trade a little latency for durability you can actually trust.

Step 5 · When a broker dies

Leader election

A leader broker crashes mid-flight. Someone has to notice, promote a healthy in-sync replica to leader, and tell every producer and consumer where to go now — without two brokers both thinking they’re the leader.

Design decision: The leader broker for a partition crashes mid-flight. How do clients keep working?

The call: A Controller elects a new leader from the ISR over a consensus store. — The controller watches health and promotes an in-sync replica via consensus (ZooKeeper/KRaft), then propagates metadata so clients transparently reconnect. Consensus is needed only for who-leads, not every message.

A Controller watches broker health and runs leader election over a consensus store (ZooKeeper, or Kafka’s own KRaft). It promotes an in-sync replica and propagates the new metadata, so clients transparently reconnect to the new leader.

Consensus for metadata: You don’t need consensus on every message — just on who leads each partition. Keeping that small, strongly-consistent decision separate from the high-volume data path is what makes the whole thing scale.

Step 6 · The log can’t grow forever

Retention & compaction

An append-only log that never forgets eventually fills every disk. But you also can’t blindly delete — some consumers replay history, and some topics need the latest value per key kept indefinitely.

Apply a retention policy: drop log segments older than N days or beyond a size cap. For keyed topics, use log compaction to keep only the most recent message per key, so the log becomes a compact snapshot of current state.

Two ways to forget: Time/size retention suits event streams (“keep 7 days”). Compaction suits changelogs (“keep the latest state per key forever, drop the history”). Pick per topic.

Step 7 · What can it promise?

Delivery guarantees

Networks drop acks and clients crash mid-batch, so producers retry and consumers reprocess — producing duplicates. Applications need to know what the system actually guarantees.

Design decision: Retries and crashes cause reprocessing. What guarantee do you offer, and how do you avoid duplicates?

The call: At-least-once by default; idempotent producers + transactions for exactly-once. — Retry until acked (accepting dupes), then dedup by producer-id + sequence and use transactions to commit write+offset atomically. Consumers commit offsets only after processing — that ordering defines the guarantee.

Default to at-least-once: retry until acknowledged, accept possible duplicates. Layer idempotent producers (dedup by producer id + sequence) and transactions (atomic write-plus-offset-commit) to reach exactly-once within Kafka. Consumers commit offsets only after successfully processing.

Commit after processing: The ordering of “do the work” and “commit the offset” decides your guarantee. Commit after work = at-least-once; the opposite = at-most-once. Exactly-once needs idempotency or transactions on top.

You did it

You just designed a message queue.

  • A — n
  • P — a
  • C — o
  • R — e
  • A
  • R — e
  • A — t
built to be replayed, not memorized — make the calls, kill the leader, run the gauntlet.
Finished this one? 0 / 62 System Designs done

Explore the topic

See this alongside everything else on the same subject — handbooks, system designs, challenges and tools, in one place.

Cite this page

Reference it in your work, paper or an AI's context window.

APASingh, S. (2026). Design a Message Queue. Vibe Engines. https://vibeengines.com/systemdesign/message-queue-system-design
MLASingh, Saurabh. “Design a Message Queue.” Vibe Engines, 2026, vibeengines.com/systemdesign/message-queue-system-design.
BibTeX
@online{vibeengines-message-queue-system-design,
  author       = {Singh, Saurabh},
  title        = {Design a Message Queue},
  year         = {2026},
  organization = {Vibe Engines},
  url          = {https://vibeengines.com/systemdesign/message-queue-system-design}
}