Kafka defined flat: partitions and offsets, replication, how a producer batches and compresses, how the partitioner picks, retention and segments, the poll loop, offset reset, static membership, compatibility modes, Connect transforms, Streams state stores, fetch tuning, dead-letter topics, MirrorMaker, tiered storage, KRaft, headers and message size.
What is a partition in Kafka, and what is an offset? What does an offset count?
Why they ask this
It is the first question in every Kafka screen, and the answer sets the vocabulary for everything after it.
Say this
A partition is one append-only, ordered log that holds part of a topic, stored on a broker and replicated. An offset is a record's position in that partition, a number that starts at 0 and only increases. Offsets are per partition, not per topic, and a consumer's progress is one offset per partition.
The reasoning
A topic is a name. The data lives in its partitions, each a sequence of records appended in order and never modified. Kafka's broker config documents `num.partitions` at 1 for a new topic, which is why production topics are created explicitly with more.
Within one partition, records are strictly ordered, and the offset is that order: record 0, record 1, and so on. Two partitions of the same topic have their own independent offset sequences, so offset 500 in partition 0 and offset 500 in partition 1 are unrelated records.
A consumer tracks one offset per partition it reads, the position of the next record to fetch. Committing an offset stores that number so a restart can resume. Reading does not delete anything; the offset just moves forward.
Because order is per partition, any ordering guarantee Kafka gives is per partition. Parallelism is bounded by the partition count, because one consumer in a group reads a partition at a time.
The answer most people give
"The offset is a message id that is unique across the topic." It is unique only within a partition. The same number appears in every partition.
They’ll ask next
A topic has 3 partitions. A consumer group commits offsets. How many numbers does the group store, and where?
What is the replication factor? What are leader and follower replicas, and which one do clients talk to?
Why they ask this
Durability in Kafka is replication, and candidates who say "Kafka is durable" without this are repeating a slogan.
Say this
The replication factor is how many copies of each partition exist across brokers; the broker default `default.replication.factor` is 1, and production uses 3. One replica is the leader, which handles all reads and writes for the partition. The others are followers that fetch from the leader to stay in sync and take over if it fails.
The reasoning
Each partition is copied to `replication.factor` brokers. With 3, a partition survives two broker failures. With the documented default of 1, there is one copy and a broker loss loses the partition until the broker returns.
Producers write to the leader and consumers read from it by default. Followers replicate by fetching from the leader like a consumer would. A follower that keeps up within `replica.lag.time.max.ms`, documented at 30 seconds, is in the ISR, the in-sync replica set.
When a leader fails, the controller elects a new leader from the ISR. If no in-sync replica exists, the partition goes offline unless `unclean.leader.election.enable`, documented as false, is turned on, which trades data loss for availability.
Replication is also what `acks=all` waits for. The leader acknowledges a write only when every ISR member has it. `min.insync.replicas` sets the floor; it is documented at 1 and set to 2 in production.
The answer most people give
"Consumers read from followers to spread load." By default all reads go to the leader. Follower fetching exists as an opt-in for rack-aware setups, not as the default.
They’ll ask next
Replication factor 3, `min.insync.replicas` 2, two brokers down. What happens to producers and to consumers?
What is retention in Kafka? What is a log segment, and how do the two interact?
Why they ask this
Consuming does not delete, so something must, and the segment is the unit that something works on.
Say this
Retention is how long, or how much, a partition keeps records before deleting them, regardless of whether anyone consumed them. The broker default `log.retention.hours` is 168, 7 days, with `log.retention.bytes` at -1 meaning no size limit. A partition's log is stored as segment files of `log.segment.bytes`, 1 GiB by default, and retention deletes whole closed segments, never single records.
The reasoning
Kafka is a log, not a queue. A consumed record stays until retention removes it, so a second consumer group can read the same data and a failed consumer can replay. Retention is the only thing that frees disk.
A partition on disk is a directory of segment files. The active segment receives appends; when it reaches `log.segment.bytes` or `log.segment.ms`, it is closed and a new one opens. Each segment has index files for offset and timestamp lookup.
Retention checks closed segments. A segment is deleted when its newest record is older than the time limit or when the partition exceeds the size limit. The active segment is never deleted, so a quiet partition can hold records past the retention time.
`log.cleanup.policy`, documented as `delete`, chooses time or size deletion. Setting it to `compact` keeps the latest record per key instead, which is log compaction, the policy for changelog topics.
The answer most people give
"Retention deletes records older than 7 days." It deletes segments whose last record is older. A record 8 days old in the active segment is still there.
They’ll ask next
A consumer is down for 8 days with 7-day retention. What does it see when it restarts, and which setting decides?
What do `linger.ms` and `batch.size` do on a producer? What happens when each limit is reached?
Why they ask this
They are the two knobs behind every producer-throughput question, and the defaults changed, which catches people who learned them years ago.
Say this
The producer collects records per partition into a batch. `batch.size`, documented at 16384 bytes, is the batch's byte limit. `linger.ms`, documented at 5 milliseconds in Kafka 4.x, is how long the producer waits for more records before sending a batch that is not full. A batch is sent when it fills, when linger expires, or on flush.
The reasoning
One request per record would be slow. The producer buffers records in memory, grouped by partition, and sends a batch per partition in one request. Bigger batches mean fewer requests, better compression, and higher throughput.
`batch.size` caps each batch in bytes. A record larger than it is sent on its own. Raising it to 64 KB or 256 KB lets hundreds of 1 KB records travel together. The memory for all batches comes from `buffer.memory`, documented at 32 MiB.
`linger.ms` is a deliberate delay. With 0, the producer sends as soon as a sender thread is free, so batches at low rates hold one record. With 5 to 20 ms, batches fill before they leave. The producer docs say the default is now 5 ms; it was 0 in older releases.
The two interact: a batch leaves when either limit is hit. At high rates `batch.size` governs; at low rates `linger.ms` does. Compression, `compression.type` documented as `none`, applies per batch, so bigger batches compress better.
The answer most people give
"`linger.ms` adds latency to every message." It adds at most that many milliseconds, and only when the batch is not already full. At high throughput the batch fills first and linger never fires.
They’ll ask next
A producer sends 1 KB records at 20 MB/s with `linger.ms=0` and the default batch size. Which two settings do you change first?
What does producer compression do? Where are records compressed and decompressed, and what are the codec choices?
Why they ask this
Compression is the cheapest throughput win and the one place where the broker's behaviour surprises people.
Say this
With `compression.type` set to gzip, snappy, lz4 or zstd, the producer compresses each batch before sending it. The broker stores the batch as received, still compressed, and consumers decompress it. The documented default is `none`. Snappy and lz4 are fast with moderate ratios; gzip and zstd compress more and cost more CPU.
The reasoning
Compression works on the batch, not the record. That is why it pairs with `linger.ms` and `batch.size`: a batch of 500 similar JSON events compresses several times better than one event.
The broker does not decompress by default. It validates the batch and appends it as is, so compressed data costs less disk and less replication bandwidth too. The broker-side `compression.type` can override this to recompress, which doubles CPU and is rarely wanted.
Consumers receive the compressed batch and decompress in the client. So the CPU cost sits on producer and consumer, and the network and disk savings sit everywhere in between.
Choosing a codec: snappy and lz4 for latency-sensitive paths, zstd for the best ratio at acceptable CPU, gzip when a consumer cannot use anything newer. Changing the codec needs no broker change, because each batch records its own.
The answer most people give
"The broker compresses the topic to save disk." The producer compresses. The broker just keeps what it was given, which is why producer settings decide the topic's storage size.
They’ll ask next
Producers switch from none to zstd. What changes on the broker's disk, on the network, and in the consumer's CPU?
How does the default partitioner choose a partition for a record with a key, and for one without? What is the sticky partitioner?
Why they ask this
The answer explains both ordering and hot partitions, and the keyless case changed in Kafka 2.4, which many candidates missed.
Say this
With a key, the partitioner hashes the key bytes with murmur2 and takes the result modulo the partition count, so one key always lands on one partition. Without a key, the sticky partitioner sends records to one partition until a batch fills, then switches, so keyless records still form full batches. The producer docs describe this behaviour when `partitioner.class` is unset.
The reasoning
Keyed records are about ordering. All events for `order_42` hash to the same partition, so they are consumed in the order they were produced. The cost is that the key's traffic cannot spread: one very busy key is one hot partition.
Keyless records only need throughput. The old behaviour was round-robin per record, which spread one producer's records across every partition and produced tiny batches everywhere. The sticky partitioner, default since 2.4, keeps sending to one partition until `batch.size` bytes are produced to it, then rotates, so batches fill and requests shrink.
The producer docs add that `partitioner.adaptive.partitioning.enable`, documented as true, lets the producer prefer partitions on faster brokers, so a slow broker does not hold back the whole producer.
Changing the partition count changes the modulo, so a key that hashed to partition 3 of 12 may hash elsewhere with 48 partitions. Old records stay where they are, which breaks per-key ordering across the change.
The answer most people give
"Keyless records are round-robined one at a time." They were, before 2.4. The sticky partitioner batches them to one partition at a time, which is why keyless producers got faster without changing code.
They’ll ask next
You add partitions to a keyed topic during the day. What happens to ordering for a key whose hash now maps to a different partition?
What limits the size of a Kafka message, and how do you send a large payload?
Why they ask this
The first production error many people hit is a record-too-large exception, and the fix is a design, not a setting.
Say this
Three settings must agree: the producer's `max.request.size`, documented at 1 MiB, the broker's `message.max.bytes`, documented at 1,048,588 bytes, and the consumer's `max.partition.fetch.bytes`, documented at 1 MiB. Raising them works up to a few MB. For large payloads the pattern is to store the object elsewhere and send a reference.
The reasoning
A record larger than `max.request.size` is rejected by the producer before it leaves. One that passes but exceeds the broker's `message.max.bytes`, or the topic-level override, is rejected by the broker. A consumer whose `max.partition.fetch.bytes` is smaller than a record cannot make progress past it.
Kafka is tuned for many small records. Raising all three limits to 10 MB works but costs memory on every client and broker and makes batches lumpy. Compression counts after the fact, so a 5 MB JSON that compresses to 500 KB is checked against the compressed size on the broker.
The claim-check pattern avoids the problem: write the large object to object storage, and publish a small record with its location, checksum and metadata. Consumers fetch the object when they need it. The stream stays fast and the object has its own lifecycle.
Where the payload must travel in Kafka, the alternative is chunking. Split it into ordered records with the same key and reassemble in the consumer, with its own failure cases to handle.
The answer most people give
"Set `message.max.bytes` to 100 MB on the broker." The producer and consumer limits still block it, and every fetch buffer grows to match. The size limit is a design signal, not an obstacle.
They’ll ask next
Images of 8 MB must be processed by a pipeline fed from Kafka. Where do the images go, and what does the record contain?
What is the consumer poll loop? What does the heartbeat thread do, and how is that different from calling `poll()`?
Why they ask this
Two timeouts, two threads, and most rebalance storms come from confusing them.
Say this
A consumer works by calling `poll()` in a loop: each call fetches up to `max.poll.records`, documented at 500, and returns them for processing. A separate background thread sends heartbeats every `heartbeat.interval.ms`, documented at 3 seconds, to prove the process is alive. Liveness is the heartbeat; progress is `poll()`. Each has its own timeout.
The reasoning
`poll()` is where everything happens: joining the group, fetching records, committing offsets with auto-commit, and signalling that the consumer is making progress. If the application does not call `poll()` within `max.poll.interval.ms`, documented at 5 minutes, the consumer is considered stuck and leaves the group.
The heartbeat thread runs on its own. It tells the group coordinator the process is alive, independent of whether `poll()` is being called. If no heartbeat arrives within `session.timeout.ms`, documented at 45 seconds, the coordinator assumes the process died and rebalances.
So a consumer that is alive but slow, say 8 minutes processing one batch, keeps heartbeating and still gets removed, because `max.poll.interval.ms` fired. The fix is to poll more often, process fewer records per poll, or hand work to another thread.
The consumer docs note that with the new `consumer` group protocol, session timeout and heartbeat interval come from broker settings instead of the client.
The answer most people give
"Heartbeats are sent by `poll()`." They were, before 0.10.1. Now a background thread sends them, which is exactly why a stuck consumer can look alive while being kicked for not polling.
They’ll ask next
A batch takes 8 minutes to process and the group rebalances every few minutes. Which timeout fired, and which setting do you change?
What does `auto.offset.reset` do, and when does it apply? What are its values?
Why they ask this
It decides whether a new consumer reads a year of history or nothing, and it silently does not apply in the case people expect.
Say this
It tells a consumer where to start when it has no committed offset for a partition, or when its committed offset no longer exists. The documented default is `latest`, which starts at the end. `earliest` starts at the oldest retained record. `none` throws instead. Kafka 4.x adds `by_duration`, which starts a given duration back from now.
The reasoning
A brand-new consumer group has no committed offsets. With `latest` it ignores everything already in the topic and reads only what arrives after it joins, which surprises people who expected a backfill. With `earliest` it reads from the oldest retained record, which surprises people who expected to skip the backlog.
It also applies when a committed offset is out of range. That happens when the group was idle long enough for retention to delete the records. It also happens when `offsets.retention.minutes`, documented at 10,080, 7 days, expires the commit itself. Then the consumer resets according to this setting, which can mean silently skipping to the end.
It does not apply when a committed offset exists. A restarted consumer resumes from its commit regardless of this setting. To re-read, you reset the group's offsets with the admin tool or start a new group.
The consumer docs warn that `latest` can skip records when partitions are added mid-run. A new partition has no commit and starts at its end. The docs suggest `by_duration` as a safer default.
The answer most people give
"Set `earliest` to replay the topic." Only for a group with no commits. An existing group keeps its commit; replay needs an offset reset or a fresh group id.
They’ll ask next
A consumer group was off for 9 days on a topic with 7-day retention and the default reset. Where does it resume, and what was lost?
What is static membership in a consumer group? What does `group.instance.id` change about rebalancing?
Why they ask this
It is the setting that stops a rolling restart from rebalancing the whole group, and senior interviews ask for it by name.
Say this
Static membership gives a consumer a stable identity across restarts by setting `group.instance.id`. The coordinator then treats a restart as the same member coming back rather than a member leaving and a new one joining, so no rebalance happens as long as the member returns within `session.timeout.ms`. The consumer docs note that only one instance with a given id may be in the group at a time.
The reasoning
With dynamic membership, every restart is two group changes: the old member leaves, triggering a rebalance, and the new member joins, triggering another. A rolling deploy of 12 consumers is 24 rebalances, each pausing consumption.
With a static id, such as the pod name, the coordinator keeps the member's partition assignment through a disconnect. When the restarted process rejoins with the same id, it gets the same partitions and nothing else moves.
The trade is detection time. A static member that dies is only noticed after `session.timeout.ms`, so that value is usually raised for static groups, and a crashed pod holds its partitions idle until then.
Static membership pairs with the cooperative assignor, and with KIP-848's new protocol, to make membership changes incremental rather than stop-the-world.
The answer most people give
"Static membership means fixed partition assignment configured by hand." Assignment is still computed by the coordinator. What is fixed is the member's identity, so the assignment survives a restart.
They’ll ask next
Two pods start with the same `group.instance.id` by mistake. What does the coordinator do?
What do `max.poll.records`, `fetch.min.bytes` and `fetch.max.wait.ms` control? How do they trade latency for throughput?
Why they ask this
Consumer throughput tuning is three settings, and the candidate should know which side of the request each one lives on.
Say this
`fetch.min.bytes`, documented at 1, is how much data a broker must have before answering a fetch; `fetch.max.wait.ms`, documented at 500 milliseconds, is how long it may wait to reach that minimum. Those two shape the network request. `max.poll.records`, documented at 500, caps how many records one `poll()` hands the application from what was fetched.
The reasoning
A fetch request goes to the broker, which replies immediately if it has at least `fetch.min.bytes` available, or waits up to `fetch.max.wait.ms` for more. With the defaults of 1 byte and 500 ms, the broker answers as soon as anything exists, which minimises latency and maximises request count.
Raising `fetch.min.bytes` to, say, 1 MB makes the broker batch replies, cutting request overhead at the cost of up to `fetch.max.wait.ms` of extra latency on a quiet topic. `fetch.max.bytes`, documented at 50 MiB, and `max.partition.fetch.bytes` at 1 MiB cap the reply.
`max.poll.records` does not change the network at all. The consumer buffers what it fetched and hands out at most that many per `poll()`. Lowering it is how you keep processing per poll short enough to stay under `max.poll.interval.ms`.
Throughput comes from fetching big and processing in parallel across partitions. Latency comes from fetching small and often. The settings let you pick a point between.
The answer most people give
"Lower `max.poll.records` to reduce network traffic." It changes nothing on the wire. The fetch settings control traffic; `max.poll.records` controls how the buffered data is doled out.
They’ll ask next
Consumer lag grows while CPU is low and the broker shows thousands of tiny fetch requests. Which setting is wrong?
What are the Schema Registry compatibility modes BACKWARD, FORWARD and FULL? What does each one allow?
Why they ask this
It is where a schema change is accepted or refused, and the modes are named backwards by half the candidates.
Say this
BACKWARD means a consumer using the new schema can read data written with the previous one: you may delete fields and add optional fields. FORWARD means a consumer on the previous schema can read data written with the new one: you may add fields and delete optional fields. FULL is both. The transitive variants check against all earlier versions, not only the last.
The reasoning
Confluent's registry applies the configured mode when a producer registers a new schema version. BACKWARD is the default. A change that fails the check is rejected at registration, before any message is produced with it.
BACKWARD protects replay: new readers can process old records. Deleting a field is fine because the new reader does not need it. Adding a field is fine only with a default, because old records lack it.
FORWARD protects slow consumers: old readers can process new records. Adding a field is fine because the old reader ignores it. Deleting a field is fine only if it was optional, because the old reader may expect it.
FULL requires both, which in practice means adding and removing only optional fields with defaults. The transitive forms, such as FULL_TRANSITIVE, compare against every registered version, which is what you want when consumers may be many versions behind.
The answer most people give
"BACKWARD compatible means old consumers keep working." That is FORWARD. BACKWARD is about new consumers reading old data. The registry's default is BACKWARD, so by default old consumers are not protected.
They’ll ask next
Under BACKWARD, a producer adds a required field with no default. Accepted or rejected, and why?
What are single message transforms in Kafka Connect? What are they good for, and what should they not be used for?
Why they ask this
Connect questions move past "what is a connector" quickly, and SMTs are the next thing an operator meets.
Say this
Single message transforms are small, stateless functions configured on a connector that change each record as it passes through Connect: rename a field, add a timestamp, mask a value, route to a topic by a field, flatten a struct. They are for light shaping on the way in or out. Joins, aggregation and anything stateful belong in a stream processor, not in an SMT chain.
The reasoning
A source connector produces records and a sink connector consumes them. SMTs sit between the connector and Kafka, applied in a configured order to every record, one at a time, with no state and no access to other records.
Built-in transforms cover the common cases. `InsertField` adds metadata and `ReplaceField` renames or drops. `MaskField` hides values and `TimestampConverter` fixes types. `RegexRouter` picks the topic, `Flatten` unnests, and `ExtractField` lifts a key. Debezium adds its own, such as unwrapping its change envelope.
Because they run per record inside the Connect worker, a heavy transform slows every connector task. Because they are stateless, they cannot deduplicate, join or window. And because they are configuration, they are easy to deploy and hard to test.
Predicates let a transform apply only to records that match a condition, which keeps one connector serving several topics without separate configs.
The answer most people give
"SMTs are where we put the business logic." They are for shaping, not logic. Business transforms belong in a stream processor or in the warehouse where they can be tested and versioned.
They’ll ask next
A Debezium source emits before-and-after envelopes and the sink wants flat rows. Which transform, and where does it run?
What is a state store in Kafka Streams? What is a changelog topic, and what does it recover?
Why they ask this
It is how a library with no server keeps a count without losing it, and the changelog is the detail that shows understanding.
Say this
A state store is the local storage, RocksDB on disk by default or in memory, where a Kafka Streams task keeps the state of a stateful operation: a count per key, a window, a table. Every change to the store is also written to a changelog topic, a compacted Kafka topic, so if the task's instance dies, another instance replays the changelog to rebuild the store.
The reasoning
Kafka Streams runs inside your application; there is no cluster of its own. Each task owns some input partitions and keeps the state for their keys locally, which makes lookups fast and avoids a round trip per record.
Local state is fragile, so the library mirrors it. Each store has a changelog topic named after the application and store, with compaction enabled, so it holds the latest value per key. On failure, the task is reassigned, and the new instance restores the store by reading the changelog from the beginning.
Restoration time is the cost. A large store takes minutes to replay, during which the task is paused. Standby replicas, configured with `num.standby.replicas`, keep a warm copy on another instance so failover is near instant.
KTables are state stores with a changelog; a KStream is stateless. Windowed aggregations keep one store entry per key per window, with retention that drops old windows.
The answer most people give
"Kafka Streams keeps state in Kafka, so the app is stateless." The app holds the state locally. Kafka holds the backup, which is why losing an instance means replaying, not querying.
They’ll ask next
A Streams app with a 50 GB store fails over and is unavailable for 20 minutes. Which setting would have avoided it?
What is a dead-letter topic? What goes into one, and what must happen to it afterwards?
Why they ask this
One bad record can stop a partition forever, and the pattern that prevents it is asked flat as well as in scenario form.
Say this
A dead-letter topic is a separate topic where a consumer or connector publishes records it cannot process, so the main partition keeps moving. Each dead-lettered record should carry the original bytes unchanged plus the error, the source topic, partition and offset, and a timestamp, usually in headers. Someone must monitor it and have a replay path.
The reasoning
A consumer that throws on a record and restarts reads the same offset again and throws again. The partition is blocked and lag grows while every other partition is fine. Moving the record aside is the only way forward that does not lose it.
In Kafka Connect, sink connectors support this directly: `errors.tolerance=all` with `errors.deadletterqueue.topic.name`, plus context headers that record the failure reason and origin. Source connectors cannot use it, because the failure happens before a Kafka record exists.
In a hand-written consumer, catch the exception, produce the raw record to the DLQ with headers describing the failure, then commit the offset so the partition advances. Retrying a few times first, with backoff, separates transient failures from permanent ones.
A dead-letter topic without an owner is a slow data loss. Alert on its growth and keep its retention long. Build the replay: fix the cause, then re-produce from the DLQ to the main topic or to a retry topic, with the same idempotent sink.
The answer most people give
"Log the bad record and skip it." The log is not replayable and nobody reads it. The record itself, with its origin, has to be kept somewhere a job can read it back.
They’ll ask next
The same setting that gives a sink connector a DLQ does nothing for a source connector. Why?
What is MirrorMaker 2? How does it replicate between clusters, and what does it not guarantee?
Why they ask this
Multi-region and migration questions always reach it, and the offset-translation detail is what separates reading the docs from using it.
Say this
MirrorMaker 2 is Kafka's cross-cluster replication tool, built on Kafka Connect. It consumes topics from a source cluster and produces them to a target, prefixing topic names with the source cluster alias by default, replicates topic configs and ACLs, and translates consumer-group offsets so a consumer can fail over. It does not preserve offsets one to one, and it is asynchronous, so the target lags.
The reasoning
Clusters do not replicate to each other natively. MirrorMaker 2 runs as Connect connectors: a source connector reads the origin cluster, a checkpoint connector mirrors consumer offsets, and a heartbeat connector measures lag. It scales like any Connect deployment.
Replicated topics are renamed, `orders` becomes `east.orders` on the target, so that bidirectional setups do not loop. Record offsets differ between clusters because the target appends in its own order with its own gaps, which is why the checkpoint connector keeps an offset translation map.
Failover uses that map. A consumer group moving to the target cluster looks up where its source offset corresponds and resumes close to where it was, with some duplicates rather than loss.
It is not synchronous replication. Records are copied after they are committed at the source, so a source outage can lose the tail that was not yet mirrored. Stretch clusters and the newer cluster linking features in vendor distributions address the synchronous case differently.
The answer most people give
"MirrorMaker keeps the two clusters identical, including offsets." Offsets differ, topic names are prefixed, and the copy lags. It is replication for disaster recovery and migration, not a mirror.
They’ll ask next
A consumer fails over to the mirrored cluster. Why might it reprocess some records, and why is that the preferred failure?
What is KRaft? What does the controller quorum store, and what did it replace?
Why they ask this
ZooKeeper is gone as of Kafka 4.0, and the replacement is a reliable question for anyone who learned Kafka before that.
Say this
KRaft is Kafka's own consensus protocol, a Raft implementation, used by a quorum of controller nodes to hold the cluster's metadata: brokers, topics, partitions, leaders, ISR, configs and ACLs. The active controller writes metadata as records to an internal log that the others replicate. It replaced ZooKeeper, which was removed entirely in Kafka 4.0.
The reasoning
Before KRaft, cluster metadata lived in ZooKeeper and one broker acted as controller by taking a ZooKeeper lock. That meant two systems to operate, a controller failover that re-read all metadata, and a practical ceiling on partition count.
With KRaft, a small set of nodes, typically 3 or 5, run the controller role. They elect a leader by Raft, and that active controller appends every metadata change to the `__cluster_metadata` log. Followers replicate it, and brokers fetch it to keep a local copy, so broker startup and failover are reading a log, not scanning a tree.
Controllers can be dedicated nodes or combined with the broker role on small clusters. The quorum needs a majority to accept writes, so 3 controllers tolerate 1 failure and 5 tolerate 2.
Kafka 3.3 made KRaft production-ready, 3.x supported migrating a ZooKeeper cluster, and 4.0 removed ZooKeeper mode, so a cluster must migrate on a 3.x release before upgrading.
The answer most people give
"KRaft is a new broker that replaced ZooKeeper's role in message storage." ZooKeeper never stored messages. KRaft replaced it as the store for metadata and leader election.
They’ll ask next
A cluster runs 3 controllers and 2 die. What still works, and what does not?
What is tiered storage in Kafka? What stays on the broker's disk, and what moves to object storage?
Why they ask this
It changes the economics of long retention, and it is new enough that it separates current knowledge from old.
Say this
Tiered storage lets a broker copy closed log segments to remote object storage such as S3 and delete them locally after a shorter local retention, while the topic's total retention can be much longer. The active segment and recent segments stay on local disk; older data is fetched from the remote tier on demand. It is enabled per topic on a cluster with `remote.log.storage.system.enable`.
The reasoning
Without tiering, retention is bounded by broker disks, and adding retention means adding brokers. Replaying a year of events meant keeping a year on expensive, replicated disks.
With tiering, a segment is uploaded once it is closed, and the local copy is removed after `local.retention.ms` or `local.retention.bytes`. Consumers reading recent data hit local disk as before. A consumer replaying old offsets is served from the remote tier, slower but complete.
Only the leader uploads, and the remote copy is one copy, so replication factor no longer multiplies the cost of history. Compacted topics are not supported for tiering, since compaction rewrites segments.
It also makes brokers lighter: a broker that fails has less local data to re-replicate, so recovery is faster, which is the operational argument as much as the cost one.
The answer most people give
"Tiered storage archives old topics to S3." It tiers segments of a live topic. The topic stays readable end to end; only where the bytes live changes with age.
They’ll ask next
A consumer group replays 90 days on a tiered topic with 3 days local. Where do the fetches go, and what changes about their latency?
What are record headers in Kafka? What belongs in a header rather than in the payload?
Why they ask this
Headers are small, but they are where tracing, schema ids and dead-letter context live, and candidates rarely mention them unprompted.
Say this
Headers are an ordered list of key and byte-value pairs attached to a record alongside its key, value and timestamp, added in Kafka 0.11. They carry metadata that is about the record rather than part of it: a trace id, a content type, a schema version, a retry count, the origin of a dead-lettered record. Consumers can read headers without deserializing the payload.
The reasoning
Before headers, metadata had to go in the payload, which coupled every consumer to the envelope format, or in the key, which changed partitioning. Headers give a third place that neither the partitioner nor the deserializer looks at.
Typical uses: a tracing id, so a record can be followed across services. The serialization format or schema reference, so a generic consumer can pick a decoder. A retry counter for retry topics. And Connect's dead-letter context headers, naming the failing connector, the exception and the original offset.
Headers are bytes. The client gives no types, so conventions matter: agree on names and encodings across teams, and keep them small, since they travel with every record and count toward its size.
Brokers do not inspect headers. Filtering on them means fetching the record. Routing by header is a consumer-side or Connect-predicate feature, not a broker one.
The answer most people give
"Put the event type in a header so consumers can filter." Consumers still fetch every record; the broker cannot filter by header. Event type usually belongs in the payload or the topic name.
They’ll ask next
A record is dead-lettered by a sink connector. Which facts about it end up in headers, and why not in the value?
Can you reduce the number of partitions of a Kafka topic? What happens when you increase it?
Why they ask this
It is a yes-or-no question with a mechanism behind it, and the answer explains why partition counts are chosen carefully up front.
Say this
No. Kafka does not support reducing a topic's partitions, because records would have to move and offsets would change; the only route is a new topic and a migration. Increasing is allowed and instant, but existing records stay where they are and keyed records start hashing to different partitions, which breaks per-key ordering across the change.
The reasoning
A partition is a log with its own offsets. Removing one would mean either deleting its records or appending them to another partition with new offsets, which would invalidate every committed offset and every ordering assumption. Kafka refuses rather than guess.
Adding partitions is a metadata change: new empty logs appear, and new records may land in them. Nothing is rebalanced, so old partitions stay large and new ones start empty, and consumer groups rebalance to pick up the new partitions.
The ordering cost is the real one. `hash(key) % 12` and `hash(key) % 48` differ for most keys. Events for `order_42` that were on partition 3 now go to partition 27. A consumer may see the new partition's events before it finishes the old one's.
The practical rule is to size partitions for the throughput you expect in two years, with headroom. Treat a change in count as a migration, with a quiet window and consumer coordination.
The answer most people give
"You can change the partition count up or down with the admin tool." Only up. Down is a new topic.
They’ll ask next
A keyed topic goes from 12 to 48 partitions at noon. Which consumers notice, and how would you protect ordering?
What is Apache Kafka used for in a data platform? What makes it different from a database?
Why they ask this
It is the opening line of the subject and the interviewer wants the role, not the feature list.
Say this
Kafka is a distributed, durable, replayable log used to move events between producers and consumers: application events to the lake, CDC changes from databases, metrics to monitoring, and inputs to stream processors. Unlike a database it does not query or update records; it stores an ordered sequence that many consumers read independently at their own pace.
The reasoning
Kafka sits between the systems that produce data and the systems that use it. Producers write once; any number of consumer groups read the same records, each keeping its own position, and a slow consumer does not slow a fast one.
Durability and retention make it a buffer: a burst is absorbed, a downstream outage is survived, and a bug is fixed by replaying. That decoupling is the main reason to put it between a service and a warehouse instead of writing directly.
It is not a database. There are no secondary indexes, no updates in place, no queries beyond reading forward from an offset. Compacted topics give a latest-value-per-key view, but anything else is built on top in Kafka Streams, ksqlDB or a downstream store.
In a data platform the common shapes are few. Services to Kafka to a lakehouse, through Connect or Spark. Databases to Kafka through Debezium. Kafka to Flink or Streams for real-time aggregates. Kafka to a search index or a cache for serving.
The answer most people give
"Kafka is a message queue." A queue deletes on consume and delivers to one consumer. Kafka retains and lets many consumers replay, which is a log, not a queue.
They’ll ask next
Why is writing events straight into the warehouse worse than passing them through Kafka first?
What is a Kafka broker? What does one broker hold, and what does a cluster of them give you?
Why they ask this
A one-liner that checks the candidate can place partitions on machines.
Say this
A broker is one Kafka server process. It stores some partitions of some topics on its local disk, serves produce and fetch requests for the partitions it leads, and replicates the partitions it follows. A cluster spreads partitions and their replicas across brokers, which is what gives Kafka parallelism and fault tolerance.
The reasoning
Each broker has an id and owns a set of partition replicas. For the ones it leads, it accepts writes and serves reads. For the ones it follows, it fetches from the leader. A single broker can hold thousands of partitions.
Clients connect to any broker to fetch metadata, learn which broker leads each partition, and then talk to leaders directly. There is no router in the path, which is why throughput scales with brokers.
A cluster's capacity is the sum of its brokers' disks and network, divided across partitions. Adding a broker adds capacity only once partitions are reassigned to it, which is an explicit operation.
With KRaft, some nodes also run the controller role that holds cluster metadata; on small clusters those are the same processes as the brokers.
The answer most people give
"The broker routes messages to consumers." Consumers pull from the broker that leads their partitions. The broker stores and serves; it does not push.
They’ll ask next
A cluster has 3 brokers and a topic with 12 partitions at replication factor 3. How many partition replicas does each broker hold?
What is a partition leader? How is a new one chosen when a broker fails?
Why they ask this
Leader election is the mechanism behind every "what happens when a broker dies" answer.
Say this
The leader is the one replica of a partition that handles all produce and fetch requests; the followers copy from it. When the leader's broker fails, the controller picks a new leader from the in-sync replicas and tells clients through metadata, so producers and consumers reconnect to the new leader after a brief error.
The reasoning
Having one leader per partition is what keeps writes ordered: every append goes through one broker, which assigns the next offset. Followers replicate that log exactly.
The controller watches broker liveness. When a broker drops, every partition it led needs a new leader. The controller chooses from the ISR, prefers the replica listed first in the assignment when it is in sync, and writes the change to the metadata log.
Clients see a `NotLeaderOrFollower` error on their next request, refresh metadata, and retry against the new leader. With `acks=all` and `min.insync.replicas` of 2, nothing acknowledged is lost in the handover.
If no replica is in sync, the partition is unavailable until an ISR member returns. An unclean election, off by default, would promote a stale follower instead, and lose the records it never got.
The answer most people give
"Producers write to all replicas." They write to the leader only. The leader's replication to followers is what `acks=all` waits for.
They’ll ask next
The preferred leader comes back after an outage. Does leadership move back, and what setting decides?
What is a topic's retention policy? What is the difference between delete and compact?
Why they ask this
A flat question whose second half catches people who only know the time limit.
Say this
The retention policy, `log.cleanup.policy`, decides how a partition sheds old data. `delete`, the documented default, removes segments older than `log.retention.hours`, 168 by default, or beyond `log.retention.bytes`. `compact` keeps the most recent record for every key and removes older records with the same key, so the topic becomes a latest-state table that never grows beyond its key count.
The reasoning
Delete retention suits event streams: clicks, orders, metrics. The data is needed for a window and then not at all, and the window sets the disk cost.
Compaction suits changelogs: a user's current profile, a product's current price, a Streams state store's backup. A consumer reading the whole topic gets the latest value per key and nothing stale, which is how a KTable or a Connect offset topic is rebuilt.
Compaction runs in the background on closed segments, keeping the head of the log uncompacted for a while. A record with a null value is a tombstone: it deletes the key after `delete.retention.ms`, long enough for consumers to see the deletion.
A topic can use both policies together, compacting old data and still deleting it past a time limit, which bounds a changelog that keeps acquiring new keys.
The answer most people give
"Compaction compresses old segments." It removes superseded keys. Compression is a producer codec and has nothing to do with the cleanup policy.
They’ll ask next
Why must a Kafka Streams changelog topic be compacted rather than time-retained?
How is Kafka different from a message queue such as RabbitMQ? When would you choose each?
Why they ask this
It is the most common comparison on screens and the answer has to be about the model, not the throughput numbers.
Say this
Kafka is a log: records are retained for a period, consumers pull and track their own offsets, and many groups can read the same data and replay it. A queue such as RabbitMQ pushes each message to one consumer and removes it on acknowledgement. Choose Kafka for streams that several systems consume or may replay; choose a queue for task distribution where each job runs once.
The reasoning
The storage model is the difference. Kafka appends to a partitioned log and keeps it; a consumer's position is a number it stores. RabbitMQ holds messages until delivered and acknowledged, then forgets them, with per-message routing through exchanges and bindings.
That gives Kafka replay, multiple independent readers, and ordering per partition. The cost is per-message features: no per-message acknowledgement to the broker, no priority, no routing beyond topic and partition. It gives RabbitMQ flexible routing, dead-lettering and work-queue semantics, at the cost of replay and of fan-out to many readers.
Scale differs too. Kafka partitions scale consumers by partition count and handle millions of messages per second on commodity disks. Queues scale by adding consumers to one queue and are usually bounded by memory and acknowledgement rate.
Data pipelines are streams with several consumers and a need to replay, which is why Kafka is the default there. Background jobs, retries with per-task acknowledgement, and request-reply patterns are queue work.
The answer most people give
"Kafka is just a faster RabbitMQ." Different model, different guarantees. The speed follows from the log design, not from better engineering of the same idea.
They’ll ask next
Three teams need the same order events, one of them to rebuild a cache from scratch monthly. Which system, and why?
EvergreenReported · 2Delivery semanticsConsumer lag & throughput tuning
What is the difference between Kafka and Amazon Kinesis Data Streams? What do you give up with each?
Why they ask this
AWS-heavy loops ask it flat, and the answer should name the units, shards against partitions, and the limits.
Say this
Both are partitioned, durable logs with replay. Kinesis is a managed AWS service with shards as the unit, documented at 1 MB/s or 1,000 records/s of writes and 2 MB/s of reads per shard, retention from 24 hours up to 365 days, and on-demand or provisioned capacity. Kafka is open source, runs anywhere, has larger ecosystems for Connect and Streams, and must be operated or bought as a managed service such as MSK.
The reasoning
The model is the same: a partition key hashes a record to a shard or partition, ordering holds within it, consumers track positions. Someone who knows one can read the other's docs in an afternoon.
Kinesis removes operations. No brokers to size, resharding by API, on-demand mode that scales with traffic, and a retention knob instead of disks. Its limits are per shard, and enhanced fan-out is needed when several consumers read at full speed.
Kafka gives control and portability: no per-shard throughput caps, compaction, transactions, exactly-once between topics, Kafka Streams, and the Connect ecosystem. It costs an operator or a managed fee, and it ties ordering to a partition count you chose.
Choose Kinesis inside AWS when the workload fits its limits and the team is small. Choose Kafka when you need its ecosystem, run outside one cloud, or need the semantics only Kafka has.
The answer most people give
"Kinesis is Amazon's Kafka." Same shape, different limits and ecosystem. Saying so without the shard numbers or the missing Streams and Connect is the answer the interviewer is testing for.
They’ll ask next
A consumer needs exactly-once from one stream to another. Which system offers it natively?
What is a producer callback? Why should a producer never ignore the result of `send()`?
Why they ask this
Silent data loss in producers comes from fire-and-forget, and the callback is the one-liner that prevents it.
Say this
`send()` is asynchronous: it returns a future and queues the record. A callback is the function the producer calls when the broker acknowledges the record or when sending finally fails after retries and `delivery.timeout.ms`, documented at 2 minutes. Ignoring both means a rejected record is lost without anyone knowing.
The reasoning
The producer batches in the background, so `send()` cannot know the outcome. It returns immediately. The outcome arrives later, through the future or the callback, with either the record's partition and offset or an exception.
A callback that logs and counts failures is the minimum. A callback that writes the failed record to a local failure store is what an ingestion layer needs. A producer that drops records on the floor is at-most-once without saying so.
Retries happen before the callback fires: the producer retries transient errors on its own, up to `delivery.timeout.ms`, and only then reports failure. The idempotent producer, documented as enabled by default, makes those retries safe from duplicates.
Calling `.get()` on every future makes sending synchronous and throughput collapses. The callback keeps the pipeline asynchronous while still observing every result.
The answer most people give
"If `send()` returns without an exception, the record was written." It was queued. The broker's answer comes later, and only the callback or the future carries it.
They’ll ask next
A producer logs nothing and the topic is missing 2 percent of events. Which setting and which code path do you check?
What is a Kafka connector? What is the difference between a source connector and a sink connector?
Why they ask this
Connect is how most data enters and leaves Kafka, and the one-liner checks the two directions are clear.
Say this
A connector is a configured job that Kafka Connect runs to move data between Kafka and another system without custom code. A source connector reads an external system, such as a database log with Debezium or a file drop, and writes records to topics. A sink connector reads topics and writes to an external system, such as S3, a warehouse or Elasticsearch.
The reasoning
Connect is a framework of workers. A connector is a JSON configuration naming the plugin, the topics, the external endpoint and the number of tasks. Workers split the connector's work into tasks and run them, rebalancing when a worker dies.
Source connectors own the hard part of ingestion. They poll or tail the source. They track their own offsets in Connect's internal topic, so a restart resumes. And they convert records to Kafka's format with a converter such as Avro or JSON.
Sink connectors own delivery: consuming with a group, batching writes to the target, committing offsets after a successful write, and routing failures to a dead-letter topic when configured.
Connect gives you offsets, retries, scaling and a REST API for free. The price is that complex logic does not fit; it is for moving data, with light shaping through single message transforms.
The answer most people give
"A connector is a client library for a language." That is a client. A connector is a running job inside Connect that moves data for you.
They’ll ask next
Adding tasks to a Debezium source connector does not make it faster. Why not?
How does a follower replica stay in sync with its leader? What is the high watermark?
Why they ask this
The high watermark is the detail behind "what can consumers read", and it comes up flat in senior loops.
Say this
A follower runs replica fetcher threads that request records from the leader like a consumer would and append them to its own log. The leader tracks each follower's fetch position. The high watermark is the highest offset that every in-sync replica has; consumers can only read up to it, so a record is never visible until it is replicated to the whole ISR.
The reasoning
Replication is pull. Followers fetch from the leader in a loop, and the leader learns how far each follower has got from the offsets they ask for. A follower that falls more than `replica.lag.time.max.ms`, documented at 30 seconds, behind is dropped from the ISR.
The leader advances the high watermark to the minimum of the ISR members' log end offsets. Records above it exist on the leader but are not yet safe, because a leader failure could lose them if a follower without them is elected.
Consumers fetch up to the high watermark only. That is the visibility rule that makes `acks=all` meaningful. An acknowledged record is below the watermark, so it survives a failover. No consumer ever sees a record that later disappears.
When a follower rejoins after being out of sync, it truncates its log to the leader's state and catches up. Only then is it readmitted to the ISR. That is why a long-lagging replica can take minutes to count again.
The answer most people give
"Consumers can read a record as soon as the leader has written it." They wait for the high watermark. A record only the leader holds is invisible until the followers have it.
They’ll ask next
The ISR shrinks to the leader alone with `min.insync.replicas=2`. What happens to the high watermark and to producers?
What is an offset commit? What is committed, where, and what does auto-commit do?
Why they ask this
A one-liner with the exact number that is committed as its trap.
Say this
An offset commit stores a consumer group's position for a partition in the internal `__consumer_offsets` topic, keyed by group, topic and partition. The number committed is the offset of the next record to read, one past the last processed. With `enable.auto.commit`, documented as true, the consumer commits the positions from the previous `poll()` every `auto.commit.interval.ms`, 5 seconds, inside the next `poll()` call.
The reasoning
The commit is what a restart resumes from. It is stored on the brokers, in a compacted topic replicated `offsets.topic.replication.factor` times, documented at 3, so it survives broker failure and lets any instance of the group pick up.
Committing the next offset rather than the last one means a consumer that restarts reads exactly the record after the one it finished. Off-by-one here is a duplicate or a skipped record.
Auto-commit is at-least-once at best: it commits what the last poll returned, whether or not processing finished, and only when `poll()` is next called. A crash between poll and commit replays the batch; a crash after commit but before the work completed loses records.
Manual commit, with `commitSync()` after processing or `commitAsync()` with a callback, gives control. Committing offsets together with the output, in the sink's own transaction, is how a consumer gets effectively-once.
The answer most people give
"The consumer commits the offset of the last message it read." It commits the next one. A group that commits the last offset re-reads one record on every restart.
They’ll ask next
A consumer polls 500 records, processes 200, and crashes with auto-commit on. On restart, where does it continue?