Batch, Streaming, and Real-Time Serving Trade-offs Questions
Reasoning about when to use batch, micro-batch, or continuous streaming and how to serve low-latency analytics: latency, cost, complexity, and correctness trade-offs; lambda vs kappa architectures; and reprocessing semantics. Covers real-time aggregation, freshness vs consistency trade-offs, and reconciling streaming results with batch ground truth, including geospatial and high-throughput real-time workloads under eventual consistency. The data-systems judgment topic for choosing and reconciling batch versus real-time approaches, distinct from the hands-on streaming transport itself.
Design an approach to compute per-city, per-minute aggregates (trip count, median fare, median ETA) updated every minute for dashboards. Would you use materialized views, streaming pre-aggregations, or periodic batch aggregation, and why? Justify the trade-offs in latency, cost, and accuracy, calling out anything (like a median) that doesn't aggregate as cleanly as a sum or count.
Sample Answer
Direct answer
For per-minute, per-city aggregates like trip count and median fare, I'd use streaming pre-aggregation for the additive metrics (trip count, sum of fares) and handle median separately, since a true median can't be incrementally updated the way a count or sum can; the practical approach is either a periodic recompute of the median from a bounded recent window, or an approximation (like a t-digest or histogram sketch) that can be updated incrementally.
Structured elaboration
Materialized views vs. streaming pre-aggregations vs. periodic batch aggregation, for this workload: on-demand compute (scanning raw trip events at query time) is a non-starter here, since doing that on every dashboard refresh, every minute, across every city, doesn't scale. That leaves three named options. A materialized view is a stored, incrementally-maintained result kept in sync as new data arrives, refreshed either continuously or on a trigger. Periodic batch aggregation is different in kind: a scheduled job that recomputes the aggregate from scratch over a raw window and replaces the prior value wholesale, cheap to reason about but only as fresh as its last run. Streaming pre-aggregation is a continuously-updating computation that emits an updated value on (or shortly after) every new event, the freshest of the three but the most operationally involved. The per-minute freshness bar effectively decides between the three: a tightly-scheduled periodic batch aggregation can hit a few-minutes latency cheaply and is the simplest to operate, but streaming pre-aggregation is what actually gets you a smooth, continuously-updating per-minute number without a visible "jump" every refresh cycle, and a materialized view refreshed on a normal warehouse trigger cadence usually lands somewhere in between the two on both freshness and cost, unless the warehouse specifically supports near-real-time incremental refresh.
Why median is the interesting part of this question: count and sum are associative and incrementally combinable (add the new trip's fare to a running sum, increment a counter), so a streaming aggregator can maintain them cheaply as new events arrive. Median is not: you cannot get the exact median of a growing set by combining a small update with the previous median, it requires access to the full sorted set (or an equivalent structure) for that window.
Two honest options for median: (a) maintain a bounded window of raw fares in state (feasible if the per-city, per-minute volume is small enough to hold in memory) and compute the true median from that window on each emit; (b) use an approximate percentile sketch (t-digest or a histogram-based approach) that supports incremental updates and gives a median within a known error bound, which scales to much higher volume at the cost of exactness.
Worked example
A mid-size city with roughly 200 trips/minute can comfortably hold that minute's fares in memory and compute an exact median from the sorted list, so option (a) is the right, simplest choice there. A high-volume city with 50,000 trips/minute holding a full minute of raw fares in state for an exact median starts to add real memory pressure across many cities simultaneously, so a t-digest sketch, updated incrementally per event, giving a median accurate to within a small, bounded error, is the more scalable choice, and the small accuracy trade-off is invisible to a dashboard consumer who's reading "$14.20" versus a true "$14.18".
Trade-offs and pitfalls
The pitfall in this kind of question is treating all three metrics (count, sum-derived-fare, median) the same way and reaching for one uniform technique; the right design explicitly separates the additive metrics (cheap to stream-aggregate exactly) from the median (which needs either bounded state or an approximation). The other pitfall is choosing periodic batch materialization for cost reasons and then being surprised the dashboard looks "jumpy", updating in visible steps rather than smoothly, which matters if the UX goal is a live-feeling dashboard rather than just an accurate one.
A product analytics dashboard must be updated every 10 minutes and serve thousands of users. Compare three architectures: (A) pure batch with 10-minute micro-batches, (B) a streaming engine, and (C) hybrid (CDC plus periodic batch backfills). For each, discuss cost, latency, complexity, and operational burden, and pick one with justification.
Sample Answer
Direct answer
For a dashboard that needs a 10-minute refresh and serves thousands of users, I'd pick option (A), pure batch with 10-minute micro-batches, because the freshness bar is well within micro-batch's comfortable range and it avoids paying for always-on streaming infrastructure that this workload doesn't actually need; I'd reserve (B) or (C) for a tighter latency bar or a genuinely mixed-freshness requirement.
Structured elaboration
(A) Pure batch, 10-minute micro-batches: cost is the lowest of the three, since compute only runs for the duration of each micro-batch job, not continuously. Latency is bounded by the batch interval plus run time, comfortably meeting a 10-minute bar if the job itself takes a couple of minutes. Complexity is the lowest: standard scheduled-job tooling, straightforward failure recovery (rerun the batch). Operational burden is light: no stream-processing expertise required on the team, no state-management or watermark tuning.
(B) Streaming engine: cost is higher (always-on compute and, likely, resident state), for a latency improvement (sub-minute) the stated requirement doesn't ask for. Complexity and operational burden both rise meaningfully: the team now owns checkpointing, backpressure, and event-time semantics for a workload that didn't need sub-10-minute freshness in the first place. This option only pays for itself if the 10-minute figure understates the real requirement (see the follow-up question) or is expected to tighten soon.
(C) Hybrid, CDC plus periodic batch backfills: cost sits between the other two: you pay for a CDC pipeline (log-based change capture, generally lighter than a full stream processor since it only ships committed changes) on top of the existing batch job, so it's more than (A) alone but usually less than running (B) for every metric. Latency is genuinely mixed rather than a single number: the CDC-fed subset of metrics gets near-real-time freshness (seconds to low minutes), while everything else stays on the 10-minute batch cadence. Complexity rises meaningfully over (A): there are now two data paths to build, test, and reason about, and consumers have to know which panel is on which path. Operational burden follows complexity: the team owns both the batch scheduler and the CDC pipeline's health (replication lag, connector failures), more monitoring surface than (A) alone, though usually less than operating a full stream processor the way (B) requires. This option is worth its cost only if some subset of the dashboard's metrics genuinely need faster-than-10-minute updates (say, an incident-monitoring panel embedded in an otherwise 10-minute dashboard); if the entire dashboard genuinely only needs 10 minutes uniformly, (C) adds this cost and complexity without a matching benefit.
Worked example
An internal ops dashboard for a logistics company needs shipment-status counts refreshed every 10 minutes for thousands of warehouse staff to check periodically. Option (A): a Spark or warehouse-native job runs every 10 minutes, reads the latest partition, recomputes the aggregates, and writes to a serving table; total infrastructure is a scheduler and a compute cluster that only runs a few minutes out of every ten. This comfortably clears the 10-minute bar with meaningful margin, at a fraction of what an always-on Flink cluster serving the same numbers would cost, since the compute is idle (and unbilled, in a serverless/ephemeral setup) most of the time.
Trade-offs and pitfalls
The recurring mistake on this kind of question is picking (B) because streaming is the more sophisticated-sounding answer, without checking whether the stated 10-minute requirement is actually the real one; always confirm whether "10 minutes" is a hard business requirement or a starting ask that will tighten soon, since building (A) now and having to redo the architecture in six months is a real cost too, just a deferred one. The other mistake is reaching for (C) reflexively whenever a requirement mentions "near-real-time," without confirming that only a genuinely distinct subset of metrics needs the faster path; if the whole dashboard shares one freshness requirement, hybrid adds complexity without adding value.
Your streaming aggregation for daily totals disagrees with the nightly batch totals by 2% for the last month. The streaming pipeline uses a 5-minute watermark and 10 minutes of allowed lateness. Walk through an investigation and remediation plan: how would you quantify the impact of late events on the discrepancy, and how would you correct the historical daily totals once you know the cause?
Sample Answer
Direct answer
A consistent 2% streaming-vs-batch shortfall with a fixed 10-minute allowed-lateness window is the classic fingerprint of the watermark dropping late events the batch layer still counts, so the investigation should start there before assuming a bug, and the fix is either widening the lateness window or explicitly reconciling the dropped tail, not rewriting the aggregation logic.
Structured elaboration
Step 1, quantify the late-event distribution: pull the true arrival-time-versus-event-time delta for a sample of production events from the raw log (not from the streaming job's output, which has already discarded what it dropped). If a meaningful fraction of events arrive more than 10 minutes after their event time, that's a sufficient explanation for a shortfall, and the size of that fraction should roughly match the 2% gap.
Step 2, confirm the mechanism, not just correlate it: rather than assuming, build the arithmetic. If X% of daily events arrive later than the 10-minute allowed-lateness window, the streaming aggregate will structurally be short by approximately X% (modulo any events that arrive within the window and are still counted). This step (below) shows a concrete, reproducible version of that check.
Step 3, remediate: two independent levers, not mutually exclusive: (a) widen the allowed-lateness window if the operational cost (more held-open state, slightly higher latency before a window finalizes) is acceptable, which recovers some of the gap directly; (b) accept that some tail will always arrive later than any reasonable window and build an explicit reconciliation step that corrects the streaming-derived historical numbers against the batch recompute on a schedule, rather than treating the streaming number as ever being the final one for past periods.
Step 4, correct history: for the past month's data, don't try to "fix" the streaming aggregate retroactively; simply replace the affected historical daily totals with the batch-recomputed values (which, since batch sees the full late tail, are the accurate ones), and communicate clearly to downstream consumers that historical numbers for that period were corrected and why.
Worked example
A synthetic reproduction of the mechanism, with all inputs pinned and seeded:
import random
random.seed(7)
N_EVENTS = 200_000
ON_TIME_FRACTION = 0.965
n_on_time = int(N_EVENTS * ON_TIME_FRACTION)
n_late = N_EVENTS - n_on_time
# late events: minutes-late drawn from an exponential with mean 25 minutes
late_minutes = [random.expovariate(1 / 25) for _ in range(n_late)]
allowed_lateness_minutes = 10
late_but_within_window = sum(1 for x in late_minutes if x <= allowed_lateness_minutes)
streaming_count = n_on_time + late_but_within_window
shortfall_pct = (N_EVENTS - streaming_count) / N_EVENTS * 100
print(f"n_on_time={n_on_time}")
print(f"n_late={n_late}")
print(f"late_but_within_window={late_but_within_window}")
print(f"streaming_count={streaming_count}")
print(f"shortfall_pct={shortfall_pct:.2f}")
Executed output: 200,000 total events (batch ground truth); 193,000 on-time; 7,000 late (of which 2,380 arrive within the 10-minute window and are still counted, 4,620 arrive later and are dropped); streaming count = 195,380; shortfall = 4,620 events = 2.31% of the batch total. This is a synthetic reproduction (not the actual production event-time histogram), but it demonstrates that a late-arrival distribution with roughly this shape, about 3.5% of events arriving late with a mean lateness of 25 minutes against a 10-minute window, is fully sufficient to produce a ~2% streaming shortfall on its own, with no bug required. A real investigation would replace this synthetic distribution with the actual measured one from step 1 and confirm the numbers line up.
Trade-offs and pitfalls
The biggest mistake here is assuming a discrepancy this size must be a bug and going straight to a code review of the aggregation logic, when the far more common cause (a mismatch between the allowed-lateness policy and the real-world late-arrival distribution) is cheap to check first and, in this case, fully explains the gap. The second mistake is widening the lateness window as a blanket fix without checking the cost: every extra minute of allowed lateness means every window stays open longer, holding more state and delaying when downstream consumers can treat a number as final, so the right lever depends on how much that delay actually costs versus how much of the 2% gap it recovers.
Tell me about a time you had to reconcile competing priorities between a stakeholder who wanted 'real-time analytics' and engineers who argued for batch processing on cost grounds. Describe how you approached the disagreement, how you evaluated the actual trade-offs, and the outcome.
Sample Answer
Direct answer
In a past disagreement like this, the resolution came from replacing the abstract argument ("real-time" versus "batch is cheaper") with a concrete number: what specific decision does the real-time data drive, and what does it cost the business, in dollars or lost opportunity, if that decision is made an hour later instead of instantly. Once that number existed, the debate stopped being about technology preference and became a straightforward cost-benefit call.
Structured elaboration
Situation: sales had promised a customer "real-time analytics" as part of a deal, without a specific latency number attached; engineering pushed back that a full streaming build-out was expensive and risky to ship on the deal's timeline, and wanted to ship a 15-minute batch refresh instead.
Approach: rather than debating in the abstract, I asked sales what the customer actually did with the data and, specifically, what would go wrong if it were 15 minutes stale instead of instant. It turned out the customer's use case was a daily operations review meeting, not a moment-to-moment trading-style decision, so "real-time" in the sales conversation had really meant "noticeably fresher than the competitor's daily-batch product," not sub-second.
Evaluation: I put together a short comparison for both sides: a true streaming build would take roughly six additional weeks of engineering time and add ongoing operational cost neither team had budgeted for, versus a 15-minute micro-batch refresh that could ship within the existing deadline and would still be dramatically fresher than the customer's status quo.
Worked example
We brought both options to the customer directly, with sales in the room, and let the customer confirm what they actually needed: they cared about seeing yesterday's promotional campaign's performance before the next morning's stand-up, not second-by-second updates. A 15-minute refresh comfortably cleared that bar. That reframed the internal conversation entirely: sales stopped pushing for "real-time" as a literal spec and started selling "fast enough to change your morning decisions," which the 15-minute batch pipeline delivered.
Trade-offs and pitfalls
The outcome was that we shipped on time with the simpler system, and it's still running years later without the operational overhead a real streaming build would have added. The lesson I took from it: "real-time" as stated by a non-technical stakeholder is almost always a proxy for some other requirement (fresher than a competitor, fast enough for a specific workflow), and the fastest way to resolve the disagreement is to find out what that underlying requirement actually is, rather than litigating the word "real-time" itself.
Design a near-real-time feature store that supports both offline batch features and online features with a 1-minute freshness target. How do you reconcile the two, so a value computed by the nightly batch job and the same value computed by the online path don't silently drift apart, and how do you version features as the reconciliation logic changes?
Sample Answer
Direct answer
The way to keep an offline batch-computed value and an online streaming-computed value from silently drifting apart is to make them provably the same computation, not two independently-tuned approximations of the same idea, by sharing the feature definition's code (or at minimum its exact specification) between both paths and continuously measuring the gap between them rather than assuming it's zero.
Structured elaboration
Reconciliation mechanism: implement the feature's transformation logic once, in a form that both the offline batch job and the online serving path can execute (a shared library, or a framework that compiles one definition to both a batch and a streaming runner). This removes the most common cause of drift, which is two engineers implementing "the same" feature slightly differently (different time-window boundary conventions, different null-handling, different rounding).
Feature versioning: attach a version identifier to every feature definition, and store that version alongside every computed value, both online and in the offline snapshot table. This lets you answer "which exact logic produced this number" months later, and lets you roll out a definition change safely (both paths read the new version's code, and old data stays tagged with the old version rather than being silently reinterpreted).
Measuring drift, not assuming it away: run a scheduled job that samples recent entities, pulls both the online-computed value and what the offline batch snapshot computed for the same entity and time, and reports the distribution of differences. Some non-zero drift is expected and fine (timing differences mean the online value reflects slightly more recent data than the batch snapshot captured), but the monitoring job should alert if the drift distribution shifts meaningfully, which usually signals a real bug (a definition mismatch, a bad deploy) rather than expected timing noise.
Handling the reconciliation logic changing over time: when the feature definition itself changes (a new bucketing rule, say), version the change explicitly, backfill historical offline snapshots under the new version if training needs consistent history, and don't silently apply the new definition to old data without re-versioning it, since that would make the drift-monitoring job (and any model trained on the old data) unable to tell what actually changed.
Worked example
A "user's 7-day purchase count" feature is defined once, in a shared library, and compiled to run both inside the online Flink job (serving reads) and the nightly batch snapshot job (training reads). The drift-monitoring job samples 10,000 users daily, compares online vs. snapshot values for the same user and day, and typically finds a small, explainable gap (the online value sometimes includes a purchase from the last few minutes that the prior night's snapshot didn't yet have). When a deploy accidentally changes the online path's window boundary from inclusive to exclusive, the drift-monitoring job's next run shows a sudden, systematic shift in the difference distribution (not just noise), which is exactly the signal that catches the bug before it silently degrades whatever model consumes the feature.
Trade-offs and pitfalls
The most common mistake is building the online and offline computations as two separate implementations "because they're different systems anyway" and trusting code review to keep them aligned; code review reliably misses subtle semantic differences (an inclusive versus exclusive window boundary looks identical in two different codebases written by two different people). The second mistake is treating any measured drift as automatically a bug and chasing it to zero; some drift is structurally expected from timing alone, and the useful signal is a change in the drift distribution, not its mere existence.
Unlock Full Question Bank
Get access to all 19 Batch, Streaming, and Real-Time Serving Trade-offs interview questions and detailed answers.
Sign in to ContinueJoin thousands of developers preparing for their dream job.