Append-only partition log with ordered match events

Apache Kafka for system design interviews

World Cup stats as motivation: producers, partitions, consumer groups — then a comparative guide to Redis queues/pub-sub/streams, SQS, Kinesis, RabbitMQ, ActiveMQ vs Kafka, plus cost/performance levers.

Why Kafka

There's a good chance you've heard of Kafka — used by a large share of the Fortune 100. If it scales the biggest companies, it's probably fair game for your next system design interview.

Apache Kafka is an open-source distributed event streaming platform that works as either a message queue or a stream processing backbone. It's engineered for high performance, scalability, and durability. With proper replication and acknowledgment settings, it provides strong guarantees against message loss.

This deep dive takes a top-down path: zoomed-out motivation first, then terminology, internals, a broker comparison (Kafka vs other queues and streams), and the interview topics seniors get pressed on. Know the basics? Skip to How it works or In your interview.

Point-to-point pipe mesh versus one durable log with many consumer groups.
LinkedIn built Kafka to escape N×M pipelines — one replayable log, many independent readers.
01Model

Topics · partitions · offsets.

02Order

Per-partition, keyed by you.

03Scale

Consumer groups · more brokers.

04Caveat

Not a blob store · not always-on consistent.

A motivating example

It's the World Cup. We run a website with real-time match statistics — every goal, booking, and substitution updates the site. Events land on a queue when they occur. The server that writes events is the producer; the server that reads and updates the site is the consumer.

Producer writing World Cup goal and substitution events into partition slots consumed by a stats website.
Simple start: one producer, partitioned queue, one consumer.

Now imagine the tournament expands to 1,000 teams, all playing at once. Events explode; a single queue host struggles. The consumer is drinking from a firehose and crashing. We add servers — but random distribution would scramble order: goals before kickoff, bookings for fouls not yet committed.

Two partitions with Argentina goal and Brazil substitution in wrong order relative to consumer expectations.
Cross-partition ordering isn't guaranteed — related events need the same partition key.

Partition by game ID. All events for one match share a partition and stay ordered. That's a core Kafka idea: messages route to partitions via a partitioning strategy — sensible defaults exist, but your key choice defines ordering guarantees.

The consumer is still overwhelmed. Add more consumers — but ensure each event is processed once. Consumer groups assign each partition to exactly one consumer in the group. Under normal operation, one delivery per consumer; on failure, at-least-once semantics may reprocess after restart.

Producer feeding two partitions read by a consumer group with three consumers.
hash(gameId) % numPartitions — scale consumers up to partition count.

We expand to basketball. Soccer stats shouldn't ingest basketball events. Introduce topics: each event belongs to a topic; consumers subscribe selectively. Soccer site → soccer topic; basketball site → basketball topic.

Three producers writing to soccer and basketball topics consumed by separate consumer groups.
Soccer and basketball topics — each sport's site subscribes to its own stream.

Terminology and architecture

A Kafka cluster is multiple brokers — individual servers storing data and serving clients. More brokers → more capacity and fault tolerance.

  • Partition — ordered, immutable, append-only sequence of messages (a log). Parallelism unit.
  • Topic — logical grouping of partitions. Multi-producer; you publish and subscribe by topic.
  • Producer / consumer — write and read. Kafka stores bytes; message format is yours.
  • Topic vs partition — topic is logical organization; partition is physical placement and scale.
Producers through Producer API to Kafka cluster brokers with topic partitions and consumer groups subscribed to Topic A and B.
Producer API → brokers (leader/follower per partition) → Consumer API → consumer groups.

Queue vs stream: both use offset commits. As a queue, one consumer group processes each message for work. As a stream, the log is retained and replayable — multiple consumer groups read independently, continuously as data arrives. The distinction is consumption pattern, not a different product. See Message queues for delivery semantics that apply regardless of broker.

How it works

A producer sends a record to a topic: optional value, key, timestamp, and headers. The key determines the partition (hashed with murmur2 by default: partition = hash(key) % num_partitions). No key → sticky/default partitioner batches then rotates — fine for throughput, no ordering guarantee.

Kafka record with Headers, Key, Value, and Timestamp fields stacked vertically.
Message / record structure — all fields optional; key picks the partition.
const kafka = new Kafka({ clientId: 'my-app', brokers: ['localhost:9092'] })
const producer = kafka.producer()

await producer.connect()
await producer.send({
  topic: 'my_topic',
  messages: [
    { key: 'game-42', value: 'goal scored' },
    { key: 'game-17', value: 'yellow card' },
  ],
})

Partition selection is two steps: (1) hash key → partition, (2) metadata maps partition → broker. The producer writes directly to the broker hosting that partition's leader.

Each partition is an append-only log. Messages get sequential offsets; consumers track progress and commit offsets back to Kafka. Default delivery is at-least-once — crash after processing but before commit means reprocessing. Exactly-once needs idempotent producers plus transactional APIs.

Partition A with messages A through E and sequential offsets 0 through 4.
Partition A — append-only log; consumers commit offset progress.

Replication: each partition has one leader (handles writes and default reads) and follower replicas on other brokers — see the cluster diagram above for leader/follower placement. Followers replicate the log; if the leader fails, an in-sync follower promotes. The cluster controller manages leadership. acks=all waits for all in-sync replicas before acknowledging the producer.

Pull-based consumption: consumers poll brokers at their own pace — controls rate, simplifies backpressure, enables batching. Slow consumers aren't force-fed.

const consumer = kafka.consumer({ groupId: 'stats-updater' })

await consumer.connect()
await consumer.subscribe({ topic: 'match-events' })

await consumer.run({
  eachMessage: async ({ partition, message }) => {
    console.log(message.value?.toString(), partition)
  },
})

When to use Kafka

As a message queue

  • Async processing the user doesn't wait for — YouTube transcode after upload; pointer in Kafka, video in S3.
  • Ordered processing — Ticketmaster virtual waiting room by arrival order (partition key = session or user).
  • Decouple producer and consumer scale — producer bursts faster than downstream can drain.

As a stream

  • Continuous real-time processing — ad click aggregation, live dashboards.
  • Multiple independent consumers of the same data — FB Live comments fan-out to display, moderation, analytics (each its own consumer group).
  • Replay and retention — new services catch up from the log without rewiring producers.

Kafka vs other queues and streams

Don't default to Kafka because it's famous. Name the shape first — one worker vs many independent readers, replay need, ops budget — then pick the broker. Full delivery semantics live in Message queues; this section is the brand-level comparison interviewers expect after you say "Kafka."

Work queues, pub/sub, durable logs, Redis family, and ActiveMQ compared.
Three families — work queues, ephemeral pub/sub, durable logs — plus Redis and classic JMS.

Quick comparison

OptionModelReplay / retentionOpsBest when…
KafkaPartitioned durable log; many consumer groupsDays–weeks (or tiered)Cluster / MSKFan-out + replay + high throughput
Amazon SQSManaged work queue (competing consumers)Short (visibility timeout; no log replay)Fully managedOne job, one worker; email, thumbnails
Amazon Kinesis Data StreamsShard-based log (Kafka-like on AWS)1–365 daysManaged shardsAWS-native streaming; Kinesis Analytics / Firehose
RabbitMQAMQP broker; exchanges + queuesUntil acked (not a long log)Self-host or cloudFlexible routing, priorities, classic task queues
ActiveMQ / ArtemisJMS queues + topicsUntil consumed (store-and-forward)Java enterprise opsExisting JMS / Spring ecosystem
Redis ListsLPUSH/BRPOP queueUntil poppedRedis you already runSimple, low-volume jobs beside cache
Redis Pub/SubFire-and-forget fan-outNone — offline = missedRedisLive notifications; cache invalidation
Redis StreamsConsumer groups on a stream ID logConfigurable (capped)RedisLightweight Kafka-ish pattern; smaller scale

Redis — three different tools in one box

Redis is often misnamed as "a queue." Be precise:

  • Lists as a queueLPUSH + BRPOP (or reliable variants with RPOPLPUSH). Competing consumers, no multi-subscriber replay. Fine for small job queues when Redis is already in the stack.
  • Pub/Sub — publish to a channel; subscribers get it only if connected now. No persistence, no catch-up. Great for "user X's feed updated" live push or cache bust signals — not for orders.
  • Streams — append-only entries with IDs, consumer groups, pending entries, ACK. Closest Redis analogue to Kafka. Prefer when you want log semantics without running brokers — but don't expect Kafka-scale retention or multi-datacenter throughput.

SQS (and SNS)

SQS is the default managed work queue on AWS: producers enqueue, workers compete, visibility timeout hides in-flight messages, DLQ after N receives. Standard = at-least-once, best-effort order. FIFO = per-group order + exactly-once processing within SQS's model (still design consumers carefully).

SNS is pub/sub fan-out to SQS queues, HTTP, email, etc. Pattern: SNS topic → multiple SQS queues (one per consumer team) when you want managed fan-out without Kafka ops.

  • Choose SQS when — one logical consumer type, no multi-day replay, you want zero broker ops, volume fits SQS pricing.
  • Choose Kafka instead when — many independent consumer groups, retention/replay, or very high sustained throughput where SQS cost/ops still lose.

Kinesis Data Streams

AWS's partitioned stream: shards ≈ Kafka partitions, retention up to a year, tight integration with Lambda, Firehose, Analytics. Capacity is shard-oriented (or on-demand mode). Same interview story as Kafka — key for order, scale shards for parallelism — with AWS billing and APIs instead of brokers.

  • Choose Kinesis when — already deep in AWS, want managed shards + Lambda triggers, don't want to run MSK/Kafka.
  • Choose Kafka/MSK when — need Kafka ecosystem (Connect, Flink, MirrorMaker), multi-cloud, or team already standardized on Kafka clients.

RabbitMQ

Mature AMQP broker: exchanges route to queues (direct, topic, fanout, headers). Competing consumers, acknowledgments, dead-letter exchanges, message priorities, flexible routing keys. Messages typically leave the broker once consumed — it's a smart router + buffer, not a long-lived commit log.

  • Choose RabbitMQ when — complex routing (topic exchanges), priorities, RPC-over-queue patterns, classic microservices task queues.
  • Choose Kafka when — event sourcing / CDC / analytics fan-out with retention and independent consumer groups.

ActiveMQ (Classic) / Artemis

JMS-oriented message brokers common in Java enterprise stacks: queues (point-to-point) and topics (pub/sub), persistent store-and-forward, XA/transactions with app servers. Artemis is the next-gen broker under the ActiveMQ umbrella.

  • Choose ActiveMQ/Artemis when — org already runs JMS, Spring JMS, or needs Java EE-style messaging with existing ops playbooks.
  • Prefer Kafka/Rabbit/SQS for greenfield — unless JMS compatibility is a hard requirement.

Decision cheat sheet (say this)

  1. Single worker job, managed AWS → SQS (+ DLQ). Optional SNS for fan-out to several SQS queues.
  2. Live UI / cache bust, miss OK if offline → Redis Pub/Sub (or SSE/WebSocket tier — see Real-time updates).
  3. Small durable jobs on existing Redis → Lists or Redis Streams.
  4. Flexible routing, priorities, AMQP → RabbitMQ.
  5. Java/JMS estate → ActiveMQ / Artemis.
  6. AWS stream + Lambda/Firehose → Kinesis.
  7. Many readers, replay, high throughput, CDC/events → Kafka (or MSK / Confluent).

What to know for interviews

Depth scales with level. Juniors: producer, topic, consumer group, partition key for order. Seniors: hot partitions, offset commit timing, acks/replication. Staff: retention vs cost, exactly-once trade-offs, when managed MSK/Confluent hides ops you still need to reason about.

Scalability

One broker on good hardware: rough ballpark ~1TB storage and up to ~1M msgs/sec (message size and hardware dependent). Below that, scaling may not be the interesting conversation.

To scale: add brokers and enough partitions to use them — under-partitioned topics won't spread load. Managed Kafka (Confluent Cloud, AWS MSK) automates much of this; interviews still want your partition strategy.

Partitioning strategy

The main design decision: choose keys that spread load and preserve needed order. Bad key → hot partitions (Nike LeBron ad overwhelming one partition). Mitigations:

Hash of record key maps to partitions shared by a consumer group; hot partition highlighted.
Partition sketch — key for order; bad key → hot partition.
  • No key — even spread, lose ordering (sticky partitioner over time).
  • Random salting — append random suffix to key; redistributes load, complicates downstream aggregation.
  • Compound key — ad ID + region or user segment when attributes vary independently.
  • Backpressure — slow the producer when partition lag spikes.

Fault tolerance and durability

Durability = replication + producer acks. Replication factor 3 (1 leader + 2 followers) survives a broker loss with ISR intact.

Kafka is often described as always available, sometimes consistent. "What if Kafka goes down?" is usually the wrong question — clusters are designed for continuity. Consumer failure is the realistic scenario:

  • Offset commits — consumer records progress; restart resumes from last commit. Crash before commit → reprocess (at-least-once).
  • Rebalancing — failed consumer's partitions redistribute to survivors in the group.
  • Commit timing — in a web crawler, don't commit until raw HTML is in blob storage; smaller consumer work units mean less redo on failure.

Retries and errors

Producer retries — network blips and broker failovers; enable idempotent: true so retries don't duplicate.

const producer = kafka.producer({
  retry: { retries: 5, initialRetryTime: 100 },
  idempotent: true,
})

Consumer retries — Kafka has no built-in consumer retry (SQS does). Common pattern: failed messages → dedicated retry topic → separate consumer; after N attempts → dead letter queue (DLQ) for investigation.

Main Topic, Retry Topic, and DLQ Topic with consumer retry and dead-letter flow arrows.
Consumer retries — route failures to a retry topic, then DLQ after final failure.

Cost and performance levers

Kafka design sketch

Whiteboard design sketch for an orders topic with order_id key, RF=3, retention, and two consumer groups.
Draw this shape first — key, partitions, groups, RF/retention, then sizing and failure notes.

Sizing sketch

Partitions ≥ max parallel consumers in a group. Too many partitions → metadata/memory overhead. Replication factor 3 in prod. Disk = produce_rate × retention × RF. Call these out when drawing Kafka.

Failure modes to mention

Call out at least one dependency failure (DB down, cache stampede, queue lag, region outage) and your mitigation (timeouts, retries with jitter, degraded mode, circuit breaker).

Interview Q&A by level

Practice saying these out loud for Kafka — and redraw the sketch from memory after each answer. Interviewers grade clarity and judgment more than buzzwords.

Whiteboard sketch of producers appending to partitioned topic with two independent consumer groups.
Entry sketch — one log, many independent readers with their own offsets.
Whiteboard sketch of hash key to partitions assigned to consumers in one group, with a hot partition called out.
Mid sketch — same key → same partition; watch hot partitions from bad keys.
Side-by-side whiteboard: simple SQS-style work queue versus Kafka log with independent consumer groups.
Principal sketch — name the shape (work queue vs durable fan-out) before the brand.
Interview takeaway

Match depth to the bar: define → trade off → operate. Don't dump principal answers in an entry-level screen. Close the laptop and redraw each sketch once.

Wrapping up

When you propose Kafka, also name your levers: partition count for parallelism, producer batching/compression for throughput, RF + acks for durability vs cost, and retention (or tiered/S3 archive) so hot disk doesn't grow forever.

Kafka is a distributed commit log: producers append to partitioned topics; consumer groups read in parallel with partition-level ordering; replication and acks trade latency for durability. Lead with partition strategy and hot-partition mitigations; mention at-least-once and idempotent consumers; don't put multi-megabyte payloads on the bus. When the prompt is a simple work queue, say SQS/Rabbit/Redis Streams first — save Kafka for fan-out and replay.

For queue semantics, delivery guarantees, and poison-message patterns that apply across brokers, start with Message queues. Broker pick matrix: see Kafka vs other queues and streams. For absorbing write bursts before the database, see Scaling writes. For event-driven saga choreography, see Multi-step processes. When you need stateful stream aggregation on top of Kafka, see Apache Flink; for nightly batch analytics, see Apache Spark. For how Kafka historically used ZooKeeper (and KRaft today), see the coordination deep dive. For where Kafka sits among Postgres, Redis, and SQS, see Key technologies.

Using this for live fan-out? See Real-time updates for protocol choice and the two-hop model.

← Lattice