Design problems
Classic building blocksHardSDE-3 40 min

Design a distributed message queue (like Kafka)

Asked at
AmazonFlipkartLinkedInPhonePeSwiggyUber

The interviewer asks

“Design a distributed messaging system like Kafka. Producers publish messages to topics, many consumer groups read them independently, messages are kept for days so they can be replayed, and the system must handle millions of messages per second without losing acknowledged data.”

What a strong answer looks like

A strong answer explains the central idea first: a topic is split into partitions, each partition is an append-only log on disk, and consumers track their own position (offset) instead of the broker deleting messages after delivery. That design gives ordering within a partition, replay, many independent consumer groups and very high throughput from sequential I/O, batching and zero-copy sends. Then cover replication (leader per partition, followers, in-sync replicas, acks=all with min.insync.replicas=2), the controller and metadata (ZooKeeper in old Kafka, KRaft today), consumer groups and rebalancing, retention and compaction, and delivery semantics: at-least-once by default, idempotent producers to remove duplicates on retry, transactions for exactly-once inside Kafka, and effectively-once end to end via idempotent consumers. Senior candidates discuss partition count choices, hot partitions, tiered storage and cross-region replication.

Concepts to mention

partitioned append-only log with offsetsordering only within a partition; key choiceconsumer groups and rebalancingleader/follower replication with ISR and high watermarkacks=all with min.insync.replicas=2idempotent producer and transactionsexactly-once inside Kafka vs effectively-once end to endbatching, compression, page cache, zero-copyretention, log compaction, tiered storageKRaft controller for metadatadead-letter handling for poison messages

1 Requirements

Functional

  • Create topics with a number of partitions and a replication factor
  • Producers publish messages (key, value, headers); messages with the same key go to the same partition, in order
  • Consumers in a consumer group share partitions; each message is processed by one member of each group
  • Consumers commit offsets and can rewind to replay from an offset or a timestamp
  • Retention by time or size (e.g. 7 days), and log compaction (keep latest value per key)
  • Acknowledged messages are not lost when a broker fails

Non-functional

  • Throughput: 5M messages/s in (avg 1 KB) ≈ 5 GB/s, 15 GB/s out (3 consumer groups on average)
  • Produce latency p99 under 20 ms with acks=all; end-to-end p99 under 100 ms
  • Durability: no acknowledged message lost if one broker or one AZ fails
  • Availability 99.99%; partition leader failover in seconds
  • Ordering guaranteed within a partition
  • Horizontal scale: add brokers and rebalance partitions online

Out of scope (say it out loud)

  • Rich per-message routing like RabbitMQ exchanges
  • Per-message acknowledgement and delay queues (mention how SQS differs)
  • Stream processing logic (Flink, Kafka Streams) beyond mention

2 Estimates

Ingress~5 GB/s5M msg/s × 1 KB
Replication traffic~10 GB/s between brokersReplication factor 3 → each byte is copied to 2 followers
Egress~15 GB/s3 consumer groups × 5 GB/s; recent data is served from the OS page cache, not disk
Storage for 7 days~9 PB raw (≈ 3 PB compressed)5 GB/s × 604,800 s ≈ 3 PB × 3 replicas ≈ 9 PB; with lz4/zstd batch compression (~3×) ≈ 3 PB on disk; tiered storage moves older segments to S3
Brokers~150-200Each broker ~25 Gbps NIC: budget ~100 MB/s in + 200 MB/s replication + 300 MB/s out per broker; (5 + 10 + 15 GB/s) ÷ ~0.6 GB/s ≈ 50 by network, but disk (~20 TB each for 3 PB) pushes to ~150; add headroom for AZ loss
Partitions~10-20k across the clusterSize so one partition carries ≤ ~10 MB/s and consumers have enough parallelism; e.g. a 500 MB/s topic → 64-128 partitions

3 API

EndpointWhat it does
TCP binaryProduce(topic, partition, records[], acks)Batched append; returns base offset. acks=0/1/all. Producer ID + sequence numbers make retries idempotent.
TCP binaryFetch(topic, partition, offset, max_bytes)Long-poll read from an offset; returns a batch of records (zero-copy from page cache).
TCP binaryJoinGroup / SyncGroup / HeartbeatConsumer group membership and partition assignment via the group coordinator.
TCP binaryOffsetCommit(group, topic, partition, offset)Store the group's position (in the internal __consumer_offsets compacted topic).
TCP binaryMetadata(topics)Which broker leads each partition; clients cache and refresh on errors.
AdminCreateTopic / AlterPartitionReassignmentsTopic management and moving partitions between brokers.

4 Data model

Partition logBroker local disk (append-only segment files)
segments of ~1 GB: record batches with offsettimestampkeyvalueheadersCRC

Writes only append to the active segment; old segments are deleted or compacted as a whole. Sequential I/O is fast even on HDD and great on NVMe.

Offset and time indexesBroker local disk (memory-mapped)
sparse index: offset → file positiontimestamp → offset

Find a fetch position with a binary search, then scan forward a few KB.

Cluster metadataKRaft controller quorum (Raft log)
topicspartitionsleaderISR listleader epochbroker registrations

3-5 controller nodes; replaced ZooKeeper in Kafka 3.x/4.0. Brokers replicate the metadata log and learn changes quickly.

Consumer offsetsInternal compacted topic __consumer_offsets
key = (group, topic, partition)value = committed offsetmetadata

Stored in Kafka itself, so it is replicated and compacted to the latest value per key.

Tiered segmentsS3 / object storage
closed segments + indexes

Keep hot data (hours) on local disk and the rest in S3, so retention can grow to weeks without adding brokers.

5 Architecture

CLIENTSEDGESERVICESASYNCDATAEXTERNALfind leaderProduce batch, acks=allfetch (replicate)fetch (replicate)append segmentFetch from offsetjoin group, commitstore offsetsleader + ISR changesupload old segmentscross-region copyProducers (batching)Consumer groupsMetadata (clientcache)Broker 2 (followerP0)Broker 1 (leader P0)Broker 3 (followerP0)KRaft controllersGroup coordinatorSegment files + pagecacheTiered storage (S3)__consumer_offsetstopicOther region(MirrorMaker)
ClientEdge / gatewayService / workerQueue / streamCacheDatabase / storageAI / modelThird partyrequestasync (event / queue)

Hover a box to highlight its connections.

6 Request flows

Produce with acks=all

  1. 1Producer computes partition = murmur2(key) % 64 for key 'order-123', so all events for that order go to partition 17.
  2. 2It adds the record to a per-partition batch and sends when the batch reaches 64 KB or linger.ms = 5 ms passes, compressing with lz4/zstd.
  3. 3The batch goes to the partition leader (broker 1), tagged with producer ID and sequence number.
  4. 4The leader checks the sequence (rejecting duplicates from retries), appends the batch to the active segment (OS page cache; fsync is left to the OS by default, durability comes from replication).
  5. 5Followers in the ISR fetch the new records and append them; the leader advances the high watermark when all ISR members have them.
  6. 6With acks=all and min.insync.replicas=2, the leader responds success once at least 2 in-sync replicas have the batch; the producer gets the offset.

Consume in a consumer group

  1. 1Three instances of the 'payment-notifier' service join group G; the coordinator assigns partitions (e.g. 0-21, 22-42, 43-63).
  2. 2Each instance fetches from its partitions starting at the group's committed offset; the broker sends data via zero-copy (sendfile) from page cache.
  3. 3Consumers only see records up to the high watermark, so they never read data that could be lost on leader failover.
  4. 4The instance processes records in order per partition, writing results to its DB with an idempotency check on (topic, partition, offset) or a business key.
  5. 5It commits offsets after processing (at-least-once). If it crashes before committing, the next owner reprocesses those records.
  6. 6When an instance leaves, the group rebalances (cooperative incremental rebalancing moves only the affected partitions).

7 Deep dives

The log as the core abstraction, and why it is fast

Traditional brokers (RabbitMQ, ActiveMQ) track per-message state and delete messages after acknowledgement, which means random I/O and per-message bookkeeping. Kafka's design is a partitioned, append-only log: the broker only appends and serves ranges by offset, and consumers keep their own position. This makes several things cheap. Writes are sequential appends to segment files, which disks handle at hundreds of MB/s. Reads of recent data come from the OS page cache, and the broker uses zero-copy sendfile to move bytes from page cache to the socket without copying them into the JVM. Producers batch many records and compress the whole batch, and the broker stores the batch as-is, so compression and batching benefits carry from producer to consumer. Because messages are not deleted on consumption, any number of consumer groups can read the same data, and a group can rewind to reprocess after a bug fix. The cost: ordering exists only within a partition, there is no per-message acknowledgement or delay, and a slow message blocks the rest of its partition for that group (head-of-line blocking). Mention that SQS/RabbitMQ are better for per-message work queues with individual retries.

Replication, ISR and not losing data

Each partition has one leader and followers on other brokers, usually in different AZs (replication factor 3, rack-aware placement). Followers pull from the leader like consumers. The leader tracks the in-sync replica set (ISR): followers that have caught up within replica.lag.time.max.ms (30 s by default in recent versions). The high watermark is the highest offset replicated to all ISR members, and only data below it is visible to consumers. With acks=all, the leader acknowledges a write only when all current ISR members have it, and min.insync.replicas=2 refuses writes if the ISR shrinks below 2, so an acknowledged write is always on at least 2 brokers. If the leader dies, the controller picks a new leader from the ISR, so no acknowledged data is lost. Setting unclean.leader.election.enable=false prevents an out-of-sync replica from becoming leader (which would lose data) at the cost of the partition staying offline until an ISR member returns. Leader epochs let followers truncate any uncommitted tail they had from the old leader so all replicas converge. A common interview mistake is to say 'acks=all means all 3 replicas'; it means all in-sync replicas, which could be fewer, which is why min.insync.replicas matters.

Delivery semantics: at-least-once, idempotence and exactly-once

Out of the box, a consumer that commits after processing gets at-least-once: a crash between processing and commit causes reprocessing. Committing before processing gives at-most-once (messages can be skipped). On the producer side, a retry after a timeout can write the same message twice; the idempotent producer (enabled by default since Kafka 3.0) attaches a producer ID and per-partition sequence numbers so the broker drops duplicates. Kafka transactions let a producer write to several partitions and commit consumer offsets atomically, so a read-process-write pipeline within Kafka (for example Kafka Streams or Flink with the Kafka sink) is exactly-once, and consumers with isolation.level=read_committed skip aborted data. But this guarantee stops at Kafka's boundary. If the consumer writes to Postgres or calls a payment API, you get effectively-once only by making that side idempotent: store the processed offset in the same DB transaction as the result, or use a business idempotency key with a unique constraint. Interviewers listen for exactly this distinction.

Partitions, keys and consumer scaling

Partitions are the unit of parallelism and ordering. A consumer group can have at most as many active consumers as partitions, so a topic with 12 partitions cannot use 40 consumers. Choosing the key decides ordering and balance: key by order_id to keep each order's events ordered; do not key by merchant_id if one big merchant produces 30% of traffic, because that creates a hot partition. Too many partitions also cost: more open files, more leader elections during failover, longer metadata updates and more memory on clients; KRaft raised practical limits to millions per cluster, but each partition still has overhead. Increasing the partition count later changes key-to-partition mapping and breaks per-key ordering during the transition, so pick a generous count up front (e.g. based on target throughput ÷ ~10 MB/s per partition, and maximum consumers needed). For slow processing, scale within a partition by processing different keys in parallel inside the consumer while committing offsets carefully (Confluent's parallel consumer pattern).

8 Trade-offs

Log-based (Kafka) vs queue-based (SQS/RabbitMQ)

Kafka gives replay, many independent consumers, high throughput and per-key ordering, but no per-message ack, retries or delays, and needs partition planning. SQS/RabbitMQ give per-message acknowledgement, visibility timeouts, dead-letter queues and delays with no partition planning, but messages are gone once consumed and throughput per queue is lower. Many systems use both: Kafka for event streams, SQS for task queues.

fsync per write vs replication for durability

Kafka by default does not fsync every write; it relies on replicas in different AZs, since a simultaneous power loss in three AZs is far less likely than one disk failing. Forcing fsync per batch improves single-node durability but cuts throughput significantly. For most systems, acks=all + min.insync.replicas=2 + rack-aware placement is the right trade-off.

Local disks vs tiered storage

Keeping 7 days on local disks needs many brokers mainly for storage, and rebalancing moves terabytes. Tiered storage (KIP-405: early access in Kafka 3.6, production-ready in 3.9) keeps hours locally and older segments in S3, making brokers lighter and retention cheap, at the cost of slower reads of old data and dependence on the object store.

9 Bottlenecks

Hot partition

A skewed key sends far more traffic to one partition than others, overloading one broker and one consumer. Fix the key (add a sub-key or salt for hot entities when strict ordering per entity is not needed), or give hot tenants dedicated topics.

Consumer lag during spikes

On a Big Billion Days sale, order events can jump 10×. If consumers fall behind, lag grows but no data is lost while within retention. Autoscale consumers up to the partition count, alert on lag in time (not just messages), and make sure catch-up reads that miss page cache do not starve real-time readers (tiered storage helps isolate this).

10 Failure handling

Broker fails

The controller notices the missing heartbeat (a few seconds), and for each partition that broker led it elects a new leader from the ISR. Producers and consumers get NOT_LEADER errors, refresh metadata and continue. Acknowledged data is safe because it was on at least min.insync.replicas brokers.

AZ outage

With rack-aware placement, each partition has replicas in 3 AZs; losing one leaves 2, which still meets min.insync.replicas=2. Clusters should run under ~60% capacity so the remaining brokers can absorb leadership and traffic.

Consumer crash loop on a bad message

A message that always fails blocks its partition for that group. Catch the error, publish the message to a dead-letter topic with the error, commit past it, and alert. Without this, one poison message can stop order processing.

11 What impresses

  • Explains why an append-only log with consumer-held offsets enables replay and many consumer groups
  • Correctly defines ISR, high watermark and why min.insync.replicas matters
  • Separates exactly-once inside Kafka from effectively-once with an external DB
  • Sizes brokers by network and disk, including replication traffic
  • Warns that adding partitions later breaks key ordering
  • Mentions KRaft replacing ZooKeeper and tiered storage for long retention

12 Mistakes

  • Claiming Kafka guarantees global ordering across a topic
  • Saying acks=all means every replica, ignoring ISR shrinkage
  • Claiming end-to-end exactly-once without idempotent consumers
  • Deleting messages after consumption, losing replay and multi-consumer reads
  • More consumers than partitions and expecting more throughput
  • Ignoring poison messages and consumer lag

13 Follow-ups

Q1How would you add delayed messages or per-message retries on top of this?
Q2How do you replicate topics across regions and fail over consumers?
Q3How does log compaction work and when would you use it?
Q4What happens to ordering when a consumer group rebalances?
Q5How would you implement multi-tenant quotas on the brokers?
Q6How do you choose the number of partitions for a new topic?

Saved on this device. Create a free account to keep it on every device.

Practise this with Sam

A real mock interview, out loud, with an honest scorecard. Free to start.

Start free