Stream Processing and Event Streaming Questions
Building on event-streaming platforms: Kafka and message queues, event sourcing, partitioning, consumer groups, exactly-once vs at-least-once delivery, and windowing. Covers handling late and out-of-order events, watermarks, and stateful stream operators. The core skill for real-time data engineering.
A regulatory request requires deleting a specific user's data from an append-only, compacted event log without breaking downstream consumers or historical replay integrity for other keys. Walk through how log compaction and tombstone records make this possible, and where the guarantees fall short.
Sample Answer
Direct answer
Log compaction with tombstones lets you satisfy a GDPR (General Data Protection Regulation) deletion request in an append-only log by writing a tombstone (a null-value record) for the user's key, which compaction eventually removes along with every prior value for that key, without needing to rewrite or delete arbitrary historical segments.
Structured elaboration
A regular delete in a mutable database just removes a row. In an append-only log, you can't retroactively edit history, so the mechanism is additive: you produce one more record for the same key, with a null value, marking it for deletion. Compaction treats this tombstone specially: once its grace period (delete.retention.ms) has passed, giving any lagging consumer time to see the deletion, compaction removes the tombstone AND every earlier record for that key, permanently erasing that user's data from the compacted topic.
Worked example
For a user requesting deletion, keyed by user_id, the deletion pipeline produces a tombstone record (key=user_id, value=null) to the compacted topic holding that user's data. Any consumer currently rebuilding state from the topic will, once it reaches this record, remove the user from its materialized view. After the grace period, the next compaction cycle removes both the tombstone and every earlier revision for that key from the log itself, so a brand-new consumer replaying the topic from scratch will never see that user's data at all.
Trade-offs and pitfalls
This mechanism only reaches the compacted topic itself: any downstream sink, cache, or derived table that already materialized a copy of the user's data before the tombstone was written needs its own deletion path, since compaction on the source topic doesn't reach into a consumer's separately-stored state. Compaction is also not instantaneous, so between when the tombstone is written and when the next compaction pass actually runs, the pre-deletion data can still technically exist on disk (though any correctly-behaving consumer honors the tombstone logically the moment it reads it, regardless of physical compaction timing).
Explain backward, forward, and full schema compatibility modes as enforced by a schema registry. For each mode, give an example schema change (adding a field, removing a field) and say whether it's allowed.
Sample Answer
Direct answer
Backward compatibility means a new schema can read data written with the old schema; forward compatibility means an old schema can read data written with the new schema; full compatibility requires both directions to hold at once, and each mode allows a different, specific set of schema changes.
Structured elaboration
Backward compatibility (the most common mode enforced) allows adding a new field with a default value (an old-schema record simply doesn't have it, and a new-schema reader fills in the default) and removing a field that had a default, but disallows adding a field with no default (an old writer's record would be missing a value the new reader has no way to fill in) or removing a field with no default. Forward compatibility is the mirror image: it allows removing a field or adding one with a default from the reader's perspective, ensuring an OLD reader can still make sense of NEW data, even data written with fields the old reader has never heard of, which it simply ignores. Full compatibility is the intersection of both sets of allowed changes, meaning effectively only additive changes with defaults (or field removals of fields that had defaults) are safe, since anything else breaks one direction or the other.
Worked example
Adding a new optional field referrer (type nullable string, default null) to an event schema is backward compatible (a new-schema consumer reading old data just gets null for the field that wasn't there) and also forward compatible (an old-schema consumer reading new data simply never sees the extra field, ignoring it), so it satisfies full compatibility too. Removing a required field with no default, or changing an existing field's type from string to integer, breaks backward compatibility (an old record won't have a valid value under the new schema's expectations) and generally breaks forward compatibility as well, which is why compatibility-mode checks in a schema registry reject that kind of change outright before it can be registered.
Trade-offs and pitfalls
Teams sometimes register schemas under NONE compatibility mode to unblock a change the stricter mode would reject, without recognizing that this removes the safety net entirely for every future change on that subject, not just the one they were trying to push through. The right response to a rejected, genuinely breaking change is almost always a new topic (or a new schema subject) with a defined migration plan for consumers, not weakening the compatibility mode on the existing one.
Design an approach to enrich a high-rate event stream with a slowly-changing dimension table (such as a product catalog or user profile) that updates infrequently. Compare caching with a time-to-live, asynchronous lookups with a fallback, and maintaining the dimension as local processor state.
Sample Answer
Direct answer
Enriching a high-rate stream with a slowly-changing dimension means choosing between caching the dimension locally with a time-to-live, doing an asynchronous lookup with a fallback for cache misses, or maintaining the dimension as local processor state kept fresh by its own change stream, and the right choice depends mainly on how stale a value you can tolerate and how large the dimension is.
Structured elaboration
A TTL-based cache (an in-memory map refreshed periodically, or on a miss) is the simplest option and works well when the dimension is small enough to fit comfortably in memory and moderate staleness (minutes) is acceptable. Asynchronous lookups (calling out to the dimension's source system on a cache miss, with a fallback value or a brief buffering delay while waiting) trade added latency and an external dependency for always-fresh data, appropriate when staleness truly can't be tolerated and the dimension is too large or too volatile to cache wholesale. Maintaining the dimension as local processor state, fed by its own change-data-capture or event stream (essentially a stream-table join where the "table" side is itself streamed as changes happen), avoids both staleness and per-event external calls entirely, at the cost of needing to consume and maintain that second stream as part of the job's own state.
Worked example
For a product catalog that changes a few times per hour and is small enough (a few hundred thousand SKUs) to fit comfortably in memory, maintaining it as local state fed by a change stream from the catalog service gives always-fresh enrichment with no per-event external call and no cache-staleness trade-off at all, the strongest option when it's actually feasible size-wise. For a much larger or more volatile dimension where maintaining full local state isn't practical, for example a 10-million-entry key-value store of user attributes too large to comfortably hold in every processing task's memory, a TTL cache accepting a few minutes of staleness is usually good enough for most product-analytics use cases (such as computing click-through rate per product), and is far simpler to operate than either alternative. A related variant of the local-state approach is maintaining a slowly-changing dimension as a full Type 2 history (keeping every prior value per key with its effective date range, not just the latest), which the same change-stream-fed local state can support if the join needs to know what a dimension's value was AT THE TIME of the fact event, not just its current value.
Trade-offs and pitfalls
Asynchronous lookups with a fallback value need a clear, deliberate answer for what that fallback actually means downstream (a stale-but-known value, or an explicit "unknown" marker), since silently substituting a default that looks like real data can quietly corrupt an aggregate without any visible error. Maintaining a dimension as local state is the most operationally robust option when feasible, but it means the job now depends on and must correctly consume a second stream, doubling the sources of failure that need monitoring.
Design a multi-region event-streaming topology where each region accepts local writes and reads with low local latency, while still maintaining a globally consistent materialized view. What replication approach and conflict-handling would you use?
Sample Answer
Direct answer
A multi-region streaming topology that keeps local writes and reads fast while still maintaining a globally consistent view means accepting eventual (not immediate) global consistency, replicating each region's local events asynchronously to every other region, and giving the application an explicit way to resolve or accept conflicts when the same entity is updated from two regions close together in time.
Structured elaboration
Requiring every write to synchronously reach every region before acknowledging it would defeat the entire purpose of local, low-latency writes, since every write would then pay full cross-region round-trip latency. The practical design instead has each region accept writes locally (low latency, immediately acknowledged), then asynchronously replicate those events to every other region's copy of the topic, so each region eventually has a complete picture of global activity, just not instantaneously. Conflict handling (two regions modifying the same entity within the replication lag window) needs an explicit, chosen strategy: last-write-wins by timestamp is simplest but can silently discard a legitimate concurrent update; a CRDT-based structure sidesteps the conflict entirely for data types that support it; or, for anything higher-stakes, routing all writes for a given entity to one designated "home" region (sacrificing true multi-region write locality for that entity specifically) removes the conflict possibility altogether.
Worked example
For a global product catalog where different regions rarely update the exact same SKU at the exact same moment, asynchronous cross-region replication with last-write-wins conflict resolution is usually acceptable, since actual conflicts are rare and low-stakes if they occur. For something higher-stakes, like an account balance that must never be double-spent across regions, routing all writes for a given account to a single home region (accepting non-local write latency for THAT account specifically, from other regions) avoids the conflict problem for the data that actually can't tolerate it, while less sensitive data elsewhere in the same platform still enjoys full multi-region write locality.
Trade-offs and pitfalls
Asynchronous replication means a region can, briefly, serve a locally-fast but globally-stale read (an entity that was just updated in another region hasn't replicated here yet); if the application can't tolerate that staleness for a specific read path, that path needs to explicitly route to the entity's home region rather than reading local replicas. The choice of conflict-resolution strategy has to be made per data type based on its actual business tolerance for a silently-dropped concurrent update, not applied blanket across the whole platform.
Explain the difference between event time, processing time, and ingestion time in stream processing, and give a concrete example where using the wrong one produces an incorrect result.
Sample Answer
Direct answer
Event time is when something actually happened in the real world; processing time is when your system happens to handle it; using processing time when event time is what actually matters produces a result that reflects your pipeline's own timing quirks (delays, retries, backpressure) rather than the reality you're trying to measure.
Structured elaboration
The two only coincide when data arrives instantly and in order, which almost never holds in a real distributed system: network delays, retries, mobile devices batching and later flushing offline events, and backpressure all mean a batch of events can arrive minutes or hours after they actually occurred, and can arrive out of the order they happened in. Any metric computed by grouping on processing time ("count of events I happened to receive this hour") answers a different, less useful question than the one grouped by event time ("count of events that actually happened this hour"), and the two answers diverge exactly when your pipeline experiences any delay or reordering, which is precisely when correctness matters most.
Worked example
A mobile app logs a purchase event at 11:58 PM while the phone is offline; the event only reaches the server and gets processed at 12:15 AM the next calendar day, once connectivity returns. A metric grouped by processing time would count that purchase in the wrong day's revenue entirely, inflating the next day and understating the true day it happened on. Grouped by event time (the 11:58 PM timestamp recorded on the device), the purchase correctly lands in the original day's total, which is the number a finance team actually needs.
Trade-offs and pitfalls
Event time requires trusting the timestamp the event carries, which is only as reliable as its source (a device with a wrong clock, or a system that stamps arrival time rather than occurrence time, undermines the whole approach); processing time needs no such trust and is simpler to reason about, which is why it's still the right choice for genuinely processing-time-relevant metrics (like measuring your own pipeline's throughput or latency, where you actually do want to know when things were handled, not when they happened). The mistake is applying one uniformly everywhere rather than choosing per metric based on what question is actually being asked.
Unlock Full Question Bank
Get access to all 34 Stream Processing and Event Streaming interview questions and detailed answers.
Sign in to ContinueJoin thousands of developers preparing for their dream job.