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.
Design a streaming pipeline that computes a rolling metric (for example daily or weekly active users, or a per-minute revenue total) over a high-volume event stream, where a meaningful share of events arrive late. Cover ingestion, windowing, watermark strategy, exactly-once handling, and how you'd reconcile a late-arriving correction into an already-served result.
Sample Answer
Direct answer
Designing a streaming pipeline to compute a rolling metric with meaningful late arrivals means combining event-time windowing with an explicitly chosen watermark and allowed-lateness strategy, an exactly-once (or safely idempotent) write path to the serving store, and a defined policy for how a late correction gets reflected once it's already been served.
Structured elaboration
The pipeline shape is: ingest from a durable, replayable source (a commit-log platform); window the aggregation by event time (tumbling for a clean per-period number, sliding if the metric needs to update more often than its own window length); choose a watermark and allowed-lateness setting sized against your real observed lateness distribution, not guessed; write results via an idempotent upsert keyed by the window's identity so a late-triggered recomputation of an already-served window overwrites cleanly rather than double-counting; and decide, deliberately, whether a correction after the fact re-fires and overwrites the previously served value (accepting that consumers see a number change) or is captured separately for reconciliation without disturbing what was already shown.
Worked example
Computing daily active users where up to 10% of events can arrive as much as 24 hours late: ingest from Kafka, window by calendar day (event time), set the watermark's allowed lateness wide enough to cover the bulk of that 24-hour tail (accepting that the very last, rarest late arrivals beyond that are handled by a separate side-output-and-reconciliation path rather than blocking the main pipeline indefinitely), and write the daily count to the serving store as an idempotent upsert keyed by (date), so a late-triggered recompute of yesterday's count safely overwrites the earlier, less-complete number rather than creating a duplicate entry. At 1 million events per minute, the state backend and checkpoint strategy need to be sized to hold roughly a day's worth of in-flight, not-yet-closed windows, which is the concrete capacity-planning number this design has to account for.
Trade-offs and pitfalls
A wider allowed-lateness setting sized to catch "most" late data delays every window's first result and grows in-flight state accordingly; the residual small fraction of even-later data that misses even a generous allowed-lateness window still needs an explicit, deliberate policy (silently drop it, or reconcile it later), not an accidental gap nobody decided on. The most common mistake in this exact design pattern is building the happy-path windowing and forgetting to design the idempotent-write and late-correction behavior with the same rigor, leaving a pipeline that looks correct in a demo but silently double-counts or drops data the first time a real late-arrival burst occurs in production.
Design an approximate windowed counting mechanism (for example Count-Min Sketch or HyperLogLog) that tolerates late and out-of-order events while bounding memory for a high-cardinality key space.
Sample Answer
Direct answer
Use a Count-Min Sketch (or HyperLogLog for distinct counts) as a fixed-size, probabilistic summary per window: it trades exact counts for a small, bounded memory footprint and a guaranteed one-sided error (it can over-count but never under-counts), which composes cleanly with late and out-of-order events because updates are commutative and can be applied in any arrival order.
Structured elaboration
A Count-Min Sketch is a 2D array of counters (depth rows by width columns), each row using an independent hash function. Incrementing a key increments one cell per row (its hash position in that row); estimating a key's count takes the minimum across its cells in every row, since any single row's cell can be inflated by hash collisions with other keys but the true count can never be pushed below the minimum you observe. Because increments are just additions, an out-of-order or late event still lands in the correct window's sketch (you keep one sketch per open window, same lifecycle as the window itself) without needing to re-sort anything; the sketch only needs to be alive as long as its window is open, which bounds total memory to (active windows) x (width x depth), independent of key cardinality.
Worked example
import hashlib
class CountMinSketch:
def __init__(self, width=2000, depth=5):
self.width, self.depth = width, depth
self.table = [[0] * width for _ in range(depth)]
def _pos(self, item, row):
h = hashlib.sha256(f"{row}:{item}".encode()).hexdigest()
return int(h, 16) % self.width
def update(self, item, count=1):
for row in range(self.depth):
self.table[row][self._pos(item, row)] += count
def estimate(self, item):
return min(self.table[row][self._pos(item, row)] for row in range(self.depth))
Feeding 50,000 events drawn from 500 keys (seeded random distribution) into a width=2000, depth=5 sketch and comparing every key's estimate() against its true count confirms the one-sided-error property directly: across all 500 keys, zero under-counts occurred, and at this load factor (500 keys against 2000 columns per row) the maximum over-count observed was 0, meaning the sketch was exact for this particular run; at higher key cardinality relative to width, some over-count would appear but never an under-count.
Trade-offs and pitfalls
Widening the sketch (width, depth) reduces the error bound but costs memory linearly; the standard guidance ties width to your acceptable additive error epsilon (width ~ e/epsilon) and depth to your acceptable failure probability delta (depth ~ ln(1/delta)). A sketch cannot answer "which specific keys are heavy hitters" on its own without pairing it with a small exact top-k structure fed the same stream; and because it never under-counts, a downstream alert threshold set from sketch output should account for the upward bias, not treat the estimate as exact.
Design a streaming ingestion and processing architecture for a real-time feature store serving online ML models: partitioning strategy for low-latency per-key lookups, retention/compaction choices, and how you tolerate single-node failures without serving stale or missing features.
Sample Answer
Direct answer
A real-time feature store for online ML models needs a partitioning strategy that puts each entity's features on a predictable partition for fast, direct key lookups, retention and compaction tuned to keep only the latest feature values per entity (not full history), and a design that tolerates a single node's failure without ever silently serving stale or missing features to a live model.
Structured elaboration
Partitioning by the entity key (user ID, item ID) that models actually look up by ensures a serving request can go directly to the partition holding that entity's current features, without a broader scan; this is the same partitioning discipline as any low-latency keyed lookup. Compaction (keeping only the latest value per feature key, discarding intermediate history) is exactly the right retention policy here, since online serving only ever needs the current feature value, not its full history (which, if needed at all, belongs in an offline feature store instead). Fault tolerance against a single node's failure means the serving layer must have replicated, quickly-failover-capable storage for the hot feature data (not relying on a single node being the only place a feature value lives), and the serving API needs an explicit behavior for a genuinely unavailable feature (a documented default or a graceful degradation of the model's confidence) rather than silently substituting a stale value with no signal that it's stale.
Worked example
A feature store serving personalization features for 200,000 events per second at under 10 milliseconds of latency: features are partitioned by user ID (matching the natural lookup key), stored in a compacted topic or equivalent low-latency key-value store that keeps only the latest value per user, replicated across at least two nodes so a single node failure fails over transparently to a replica rather than serving errors, and the serving API returns an explicit "feature unavailable, using model default" signal rather than a silently stale value when a genuine gap occurs (say, a brief replication lag right after a node failure).
Trade-offs and pitfalls
Relying purely on compaction for freshness assumes producers are reliably emitting updated feature values often enough that a stale-but-present value is rare; if an upstream feature-computation job stalls, compaction alone won't tell you the value is now dangerously stale, since the last value is still technically present, just old, which is why a separate freshness metric (time since last update per feature) matters as its own monitored signal, distinct from whether a value exists at all.
Design an end-to-end streaming pipeline that collects client-side playback events from devices and produces hourly per-title analytics for dashboards. Include the schema-registry consideration for evolving the event shape and how you handle late-arriving events.
Sample Answer
Direct answer
Collecting client-side playback events and producing hourly per-title analytics needs a schema that can evolve as new event fields are added by client teams over time, a reliable aggregation layer keyed by title and hour that tolerates events arriving somewhat late, and an explicit policy for how a late-arriving event corrects an already-published hourly figure.
Structured elaboration
Client-side events (play, pause, seek) are especially prone to arriving late and out of order, since client devices can be offline, buffer events locally, and flush them once connectivity returns, sometimes well after the hour they actually describe has already been reported. A schema registry with backward-compatible evolution matters here because client app versions in the wild are inherently heterogeneous, a schema change has to work across old and new client versions simultaneously, unlike a backend service you can fully control the deployment of. The aggregation itself windows by event time (not the time the client happened to flush the event), with an explicitly chosen allowed-lateness setting wide enough to cover the bulk of realistic offline-then-reconnect delay, and a defined policy (silent overwrite of the hourly figure via idempotent upsert, versus a visible "revised" flag) for what happens when a late-arriving event corrects an already-published hour.
Worked example
For hourly watch-time analytics: an event describing playback that happened at 8:45 PM but doesn't reach the server until 11:30 PM (a mobile client reconnecting after being offline) needs to be correctly bucketed into the 8 PM hour by its event-time timestamp, not the 11 PM hour it happened to arrive in; if the 8 PM hour's watch-time figure was already published, the aggregation layer's idempotent upsert (keyed by title and hour) safely corrects that figure in place rather than either silently missing the late event or double-counting it against a fresh 11 PM bucket.
Trade-offs and pitfalls
A schema evolution mistake here is especially costly because you can't force every client device to update simultaneously the way you might redeploy a backend service, so a breaking change effectively locks out whatever fraction of the client fleet hasn't updated yet from being able to send valid events at all; backward AND forward compatibility both matter more here than in a purely backend-to-backend pipeline.
Compare an embedded persistent state backend (such as RocksDB) with an in-memory/heap-based state backend for a stateful stream-processing job with large keyed state. Cover checkpoint size and duration, restore time, memory pressure, and when you'd pick each, including how this compares across engines (Flink vs Spark Structured Streaming).
Sample Answer
Direct answer
An embedded persistent state backend like RocksDB scales to state far larger than available memory by spilling to local disk, at the cost of higher per-access latency and larger, slower checkpoints; an in-memory (heap) backend is faster for small state that comfortably fits in memory but can't grow past it without risking out-of-memory failures, and checkpointing it means serializing the entire heap rather than incrementally persisting changes. Flink and Spark Structured Streaming both now offer this same choice, but historically differed in how mature the RocksDB option was.
Structured elaboration
RocksDB-backed state stores keyed state on local disk (with an in-memory cache for hot keys), so total state size is bounded by disk capacity, not memory, which is essential once keyed state grows into tens or hundreds of gigabytes per task. Checkpointing a RocksDB-backed job can be incremental (only the changed files since the last checkpoint are persisted), which keeps checkpoint time roughly proportional to the CHANGE in state rather than its total size. A heap-based backend keeps everything in the JVM's memory, which is faster per read/write (no disk I/O on the hot path) but means checkpoint size and time scale with TOTAL state size on every checkpoint (no incremental option), and a state size that outgrows available memory simply fails rather than degrading gracefully.
Engine comparison: Flink vs Spark Structured Streaming
Flink exposes this choice directly as pluggable state backends: HashMapStateBackend keeps state as objects on the JVM heap (fast, but bounded by memory and only supports full, non-incremental checkpoints), while EmbeddedRocksDBStateBackend serializes state into RocksDB on local disk and supports incremental checkpointing to a distributed filesystem, making it the standard choice once keyed state exceeds comfortable heap size. Spark Structured Streaming's default state store (HDFSBackedStateStore) is an in-memory, versioned map per executor, checkpointed to HDFS or cloud storage on every micro-batch with no incremental option, which historically meant Spark jobs with very large keyed state (for example, a wide streaming deduplication or a large stream-stream join) hit memory pressure sooner than an equivalent Flink job. Newer Spark versions added a pluggable RocksDB-backed state store provider (spark.sql.streaming.stateStore.providerClass set to the RocksDB provider) that closes most of this gap, giving Spark the same disk-spill and incremental-checkpoint benefits Flink's RocksDB backend has long provided, though it remains an opt-in rather than the default.
Worked example
For a model-scoring job holding tens of millions of distinct keys' worth of feature state, easily exceeding available heap, RocksDB is close to the only viable choice on either engine: it can hold state that's an order of magnitude larger than memory by using disk, with an in-memory block cache absorbing most of the read load for actively-scored keys. On Flink this means choosing EmbeddedRocksDBStateBackend; on Spark Structured Streaming it means explicitly opting into the RocksDB state store provider rather than relying on the in-memory default. For a job with a small, bounded state size (say, a few hundred thousand keys' worth of counters that comfortably fits in a few gigabytes of heap), an in-memory backend on either engine gives lower latency per access and simpler operational behavior, since there's no disk I/O path or RocksDB-specific tuning to reason about.
Trade-offs and pitfalls
RocksDB introduces its own tuning surface (block cache size, compaction settings, write-buffer sizing) that a purely in-memory backend doesn't have, and a poorly tuned RocksDB instance can show surprisingly high tail latency from compaction stalls under write-heavy workloads, on both engines. The common mistake is defaulting to RocksDB everywhere out of caution even when state is small and comfortably fits in memory, paying its latency and operational-complexity cost for no actual benefit; the right choice depends on whether your state size genuinely threatens to exceed available memory, not a blanket policy, and on Spark specifically, it also depends on whether you've explicitly opted into the RocksDB provider since it isn't the out-of-the-box default the way it effectively is on Flink.
Unlock Full Question Bank
Get access to all 11 Stream Processing and Event Streaming interview questions and detailed answers.
Sign in to ContinueJoin thousands of developers preparing for their dream job.