End-to-End ML System Design Questions
Designing a complete machine learning system from problem to production. Covers the components and architecture of a production ML system, data flow from ingestion to serving, scalability, and integration of models into a larger product. Emphasizes the whole-system design tradeoffs that appear in ML system-design interviews.
An online-learning system updates a model incrementally from streaming production data instead of waiting for the next scheduled retrain. What stops a bad batch of incoming data from silently corrupting the model before anyone notices?
Sample Answer
Direct answer
Never let a raw incremental update reach the live serving model directly. Validate the incoming batch itself before it's used, compute the candidate update as a separate step, evaluate that candidate against a frozen holdout before promoting it, and keep versioned checkpoints so a bad promotion can be rolled back within one cycle. This turns "silent corruption" into "a failed, visible gate."
Structured elaboration
1. Pre-update data quality gate. Before a streaming batch is allowed to touch the model at all, check its schema (types, required fields) and its statistical shape against a rolling baseline, for example with the population stability index (PSI, a metric that quantifies how much a distribution has shifted between two samples). A batch that fails either check is quarantined, not silently consumed.
2. Candidate-then-promote, not update-in-place. The online learner computes a candidate parameter update from the new batch but does not serve it directly. The candidate is evaluated against a held-out set that is NOT drawn from the streaming data (so a corrupted batch can't also corrupt the thing you're judging it against) before being promoted to the serving model. A failed evaluation blocks promotion automatically.
3. Shadow evaluation before traffic exposure. Run the candidate on a copy of live traffic without it affecting responses, and compare its predictions against the current production model on business-relevant slices for a short window before flipping any real traffic to it.
4. Versioned checkpoints and bounded blast radius. Keep the last several model snapshots (weights and optimizer state) so a promoted update that turns out to be bad (e.g., a delayed label reveals it after the fact) can be rolled back to the prior checkpoint. Additionally, cap how far any single incremental step can move the model, for example bounding the update's step size or requiring two or more consecutive batches to agree in direction before a promotion is allowed, so one corrupted or adversarial batch alone cannot swing the model.
5. Contextual bandit as an online-policy variant. Instead of committing the entire live population to one continuously-updated model, maintain a small set of candidate policies (the current champion plus one or two recently-updated challengers) and route traffic among them with a contextual bandit (a bandit algorithm whose arm-selection also conditions on request context). Because reward is observed per arm, a bad update shows up as a reward drop confined to the arm it landed on, not the whole population, and the bandit itself naturally shifts traffic away from a degrading arm before a human or an offline gate has to intervene. This is the natural generalization of "gate before promote" into a system that's always running several live candidates rather than one.
Worked example
Take the pre-update data quality gate concretely. Suppose a numeric feature has a trailing 7-day baseline mean μ=50 and standard deviation σ=5, and you reject any incoming batch whose mean for that feature falls outside 3 standard deviations of the baseline.
An incoming batch has a computed mean of 68. The z-score is:
z=568−50=518=3.6Since 3.6>3, this batch is rejected by the gate and quarantined for investigation before it can contribute to any update, rather than being silently folded into the model. If instead the batch mean were 62, z=562−50=2.4, which passes the 3-sigma gate and proceeds to the candidate-update step, where the holdout-evaluation gate from point 2 is the next line of defense.
Trade-offs & pitfalls
A gate tuned too tight mistakes real, legitimate drift for corruption and starves the model of updates it actually needs; tuned too loose, it lets a bad batch through. Both failure directions need their own monitoring (how often is the gate rejecting, and is rejection rate itself trending up). Holdout-based evaluation lags whenever labels arrive with delay, so the gate is only as fresh as your label latency allows; for slow-label domains, lean harder on the input-distribution checks and bounded step size, since those don't require a label at all. The contextual bandit approach adds real operational complexity (traffic splitting, reward-signal plumbing) and is only a safety net if the reward signal itself isn't corruptible the same way the training data is; if a bad batch also biases what you count as "reward," the bandit will happily reinforce the wrong thing.
graph LR
A[Streaming batch] --> B[Data quality gate]
B -- pass --> C[Compute candidate update]
B -- fail --> Q[Quarantine]
C --> D[Evaluate on frozen holdout]
D -- pass --> E[Shadow eval on live traffic copy]
D -- fail --> R[Reject, keep current model]
E -- pass --> F[Promote: versioned checkpoint]
F --> G[Serving]
F -.rollback.-> H[Previous checkpoint]
Compare batch scoring, a low-latency hosted endpoint, and a serverless inference setup as ways to serve a model's predictions. When would you actually reach for each one?
Sample Answer
Direct answer
Batch scoring runs predictions over a large set of inputs on a schedule and writes the results somewhere for later use; a low-latency hosted endpoint keeps a model warm and always-on behind an API to answer individual requests in milliseconds; serverless inference spins compute up on demand per request and back down when idle, trading an occasional cold-start latency hit for not paying for idle capacity. Reach for batch scoring when nobody is waiting synchronously for that specific prediction; reach for a hosted endpoint when request volume is high and steady enough to justify always-on capacity and latency must be predictable; reach for serverless when traffic is bursty or low-volume enough that paying for an idle endpoint is wasteful and occasional cold starts are acceptable.
Structured elaboration
| Batch scoring | Hosted endpoint | Serverless inference | |
|---|---|---|---|
| Latency | Not on the critical path at all | Low and predictable | Low once warm, spikes on cold start |
| Cost model | Pay for a compute run, amortized over many predictions | Pay for always-on capacity regardless of volume | Pay per invocation (or per burst) |
| Best traffic shape | Nobody waits synchronously; a scheduled job is fine | Steady, high enough volume to justify fixed cost | Bursty, sparse, or unpredictable volume |
| Freshness | As stale as the last scheduled run | As fresh as the request | As fresh as the request |
| Main operational risk | A silent job failure serves stale results for days if unmonitored | Paying for idle capacity during low-traffic periods | Cold-start latency under a concurrency spike |
Worked example
Suppose keeping a model warm on a small always-on instance costs $0.50 per hour, while a serverless platform charges $0.0004 per invocation, and every request genuinely needs an individual synchronous answer (batching is not an option).
At 200 requests per day:
Hosted endpoint (monthly)=0.50×24×30=$360 Serverless (monthly)=200×30×0.0004=$2.40At this volume serverless is roughly 360/2.40≈150 times cheaper, so the decision comes down entirely to whether occasional cold-start latency is tolerable. The two costs are equal at the volume V where V×30×0.0004=360:
V=30×0.0004360=0.012360=30,000 requests per dayBelow roughly 30,000 requests per day, serverless is cheaper; above it, the always-on hosted endpoint becomes the cheaper and lower-tail-latency choice. Batch scoring sits outside this comparison entirely: scoring 10 million items once overnight on a shared batch cluster is typically far cheaper per prediction than serving each one synchronously through either of the other two options, precisely because nothing is paying for an always-on or per-invocation serving layer; the cost is traded for freshness, since results are only as current as the last run.
Trade-offs and pitfalls
A common wrong turn is choosing serverless for a latency-critical path without checking cold-start behavior under the platform's real concurrency model, since a burst of concurrent requests can each trigger a separate cold start rather than sharing one warm instance. Another is defaulting to a hosted endpoint out of habit for something that is genuinely a batch problem, such as a once-a-day digest score that never needs an always-on API. Batch scoring's biggest pitfall is treating it as maintenance-free simply because nothing is synchronous: a broken nightly job can silently serve yesterday's scores for days if nobody watches the job's completion status or output row count.
A production model's quality has quietly dropped over the last two days, and nobody noticed until a stakeholder complained. Walk through how you'd figure out what happened, and why monitoring didn't catch it sooner.
Sample Answer
Direct answer
A silent quality drop that only surfaces through a stakeholder complaint almost always means the dashboard was watching the wrong tier, system health instead of outcome health, or that ground-truth labels arrive too slowly for an outcome panel to have caught it in real time. The investigation itself is a systematic elimination across a small set of candidate causes rather than a guess, because two of the most likely causes call for completely different fixes and can look identical on a single dashboard panel.
Structured elaboration
Step 1, scope the drop: which metric, which segment (all traffic or one cohort), and does the timing line up with a deploy, a feature-pipeline change, or an upstream schema change in the last few days. This single step often narrows the candidates from several to one or two.
Step 2, the candidate causes and the fastest discriminating test for each:
| Candidate cause | What it looks like | Discriminating test |
|---|---|---|
| Data drift | Input feature distributions changed | Compare recent input distributions against the training baseline |
| Concept drift | The relationship between features and outcome changed; inputs look unchanged | Replay recent inputs through the last known-good model version; if performance also drops there, the world changed, not this model |
| Training-serving skew from a recent change | A code or feature-pipeline change introduced a mismatch | Diff the feature vectors the serving path currently produces against what the offline pipeline would produce for the same keys and timestamps |
| Label or ground-truth change | The definition or timing of the label itself shifted | Manually review a sample of recent labeled examples for a change in labeling rules or annotator behavior |
Step 3, why monitoring likely did not catch it sooner: name the specific gap rather than re-running the same investigation.
- Aggregate metrics can mask segment-level decay; a drop concentrated in a small slice of traffic barely moves an all-traffic average.
- If the outcome metric depends on delayed ground truth, "real-time" monitoring is actually watching nothing, or a proxy, for the exact window the drop occurred in, so it structurally could not have alerted the same day.
- Alert thresholds widened to reduce noisy pages eventually stop catching real but gradual decay; a slow two-day slide often sits under a threshold tuned to catch a sudden cliff.
- If the dashboard's only quality signal is a system-health proxy such as latency or error rate, it was never wired to catch a model that is healthy but wrong at all.
Step 4, close the gap once the cause is found: add the missing tier, a per-segment quality panel, a faster proxy label, or a tighter but change-point-aware threshold, so the same class of drop pages someone next time instead of waiting for a stakeholder.
Worked example
A checkout-fraud model's approvals "look off," according to a stakeholder complaint. Segment breakdown shows the drop is concentrated in mobile-app traffic only, not the web flow. The timing lines up with a mobile app release two days earlier. Replaying pre-release inputs through the current model shows no drop, which rules out concept drift; the model itself still behaves the same way on old-shaped data. Diffing the serving feature vectors for mobile-app requests against what the offline pipeline would produce shows a null device-fingerprint field for every mobile request since the release. The root cause: the new mobile app version stopped sending a field the model depends on, and the serving path silently defaulted it instead of failing loudly, a training-serving skew introduced by an upstream change entirely outside the ML team's own deploy.
Trade-offs and pitfalls
Rolling back the most recent ML-side deploy is the reflexive first move, but if the cause is upstream, as in the worked example, a rollback does nothing and burns the response window while the real cause keeps running. Treating this as "find the one root cause" instead of checking the candidates in parallel under time pressure is a common wrong turn, since data drift and skew can look identical on a single panel and only the discriminating tests, replay and feature-vector diffing, tell them apart. Widening alert thresholds after a false-positive page reduces on-call fatigue in the short term but directly increases the odds that the next real, gradual drop goes unnoticed until a stakeholder complains again; the fix belongs in smarter, segment-aware or change-point-aware thresholds, not looser ones.
A mid-sized team is deciding whether to build training and serving on managed cloud ML services or run it themselves on Kubernetes. What would push you toward one or the other, and what would make you revisit that decision later?
Sample Answer
Direct answer
Start with managed cloud ML services unless the team already has platform engineering capacity to spare or a workload that doesn't fit managed abstractions well. The two things that should make you revisit the decision later are a computable cost crossover (your usage grows past the point where the fixed cost of running it yourselves is cheaper than the managed premium) and a hard capability wall (a hardware, scheduling, or compliance need managed can't meet).
Structured elaboration
| Criterion | Managed (e.g. a vendor's hosted training/serving service) | Self-run on Kubernetes |
|---|---|---|
| Time-to-market | Fast: built-in pipelines, autoscaling, storage/identity integrations | Slower: you build CI/CD, operators, autoscaling yourself |
| Operational overhead | Low: vendor handles patching, scheduler tuning | Real: needs dedicated platform/SRE capacity for GPU drivers, storage, networking |
| Cost predictability | Predictable billing, but a management premium is baked in and fine-grained optimization is harder | Can be cheaper at scale (spot capacity, custom bin-packing) but needs engineering effort to realize |
| Vendor lock-in | Higher: proprietary endpoint formats, registries, feature stores | Lower: standard container images and portable model formats travel across clouds |
| Custom hardware / ops | Constrained to what the vendor exposes, though many now support bring-your-own-container | Full flexibility: custom kernels, newest accelerators, custom schedulers |
Decision criteria for a mid-sized team: if the priority is fast delivery with limited dedicated ops headcount, using mostly standard frameworks, choose managed. If the priority is custom kernels, bleeding-edge hardware, strict cost control at real scale, or multi-cloud portability, choose Kubernetes. A common middle path is to start managed for experimentation and the standard model lifecycle, and move only the production-critical or genuinely specialized workloads to Kubernetes once the cost/complexity trade-off is clearly favorable, which is exactly the computation below.
Worked example
The break-even isn't a philosophical call, it's a computable crossover. Using illustrative rates (explicitly hypothetical inputs, not vendor list prices, since real prices change and shouldn't be asserted as fact) to show the shape of the calculation:
Assume the managed service charges an all-in rate of $4.00 per GPU-hour (covers autoscaling, orchestration, monitoring). Assume the raw GPU-hour cost when self-managed is $3.00, but running it yourselves requires roughly half a platform engineer's time at this scale, at a fully-loaded cost of $180,000/year:
F=12180,000×0.5=7,500 per month (fixed platform overhead)Define monthly cost as a function of GPU-hours used per month, h:
costmanaged(h)=4.00h costself(h)=3.00h+7,500Set them equal to find the crossover point:
4.00h=3.00h+7,500 h=7,500 GPU-hours/monthCheck: at h=7,500, managed costs 4.00×7,500=30,000, and self-hosted costs 3.00×7,500+7,500=22,500+7,500=30,000. They match, confirming the crossover.
Below 7,500 GPU-hours/month, managed is cheaper because the fixed platform-engineering cost isn't yet justified by volume; above it, self-hosted is cheaper. The specific number is only as good as the rates and headcount assumption you plug in, but the shape of the calculation, a fixed cost against a per-unit rate difference, is what you should actually recompute with your own numbers rather than deciding by gut feel.
Trade-offs & pitfalls
Comparing only the sticker GPU-hour price ignores engineering opportunity cost on one side and ignores real operational risk (patching, scaling failures) on the other; both belong in the model, not just the rate. Don't forget to amortize the one-time migration cost into the crossover: a move that pays off at steady state can still be a net loss for the first several months. Treat this as a decision to revisit on a schedule (say, quarterly), not a one-time fork in the road, since usage volume, team headcount cost, and vendor pricing all move independently of each other.
Design a dynamic pricing system for a marketplace, such as ride-hailing delivery fees or short-term rental nightly rates, that adjusts in near real time to supply and demand without triggering a price war or feeling unfair to the people paying it.
Sample Answer
Direct answer
The system needs three layers working together: a probabilistic demand forecast that reacts quickly to real supply/demand signals, a price-elasticity estimate that turns "demand changed" into "here's the right price delta" rather than an arbitrary multiplier, and a set of guardrails (capped price movement, explanations, fairness checks) that keep the pricing feeling earned rather than extractive. The mechanism that prevents both a price war and a sense of unfairness is the same one: bound how far and how fast price can move per unit time, and always attach a plain-language reason.
Structured elaboration
Architecture
flowchart TD
A[Real-time supply and demand signals] --> B[Demand forecast model: probabilistic]
A --> C[Price elasticity model per segment]
B --> D[Price optimizer under min/max bounds]
C --> D
D --> E[Bandit-constrained exploration: capped delta]
E --> F[Served price]
F --> G[Outcome logging: bookings, cancellations]
G --> H[Causal re-estimation of elasticity]
H --> C
G --> I[Fairness and price-war monitors]
I --> D
Key design decisions and why
- Probabilistic, not point, demand forecasts. A point estimate ("120 bookings expected") gives no sense of how confident the system is going into an unusual event (a concert, a storm). A probabilistic forecast (a distribution over expected demand) lets the optimizer be conservative exactly when uncertainty is high, which is when aggressive pricing does the most reputational damage.
- Elasticity, estimated causally, drives the price delta rather than the price itself. Elasticity (how much demand moves for a given percentage price change) has to come from a causal estimate (using logged price experiments or a doubly-robust/causal-forest style estimator), not a naive correlation, because price and demand are both driven by the same underlying events (a naive correlation would show high prices during high demand and wrongly conclude high prices cause high demand).
- Bounded, bandit-style exploration instead of free optimization. The optimizer never jumps straight to its "optimal" price; a contextual bandit explores within a capped band around the current price, learning the local elasticity safely. This is what caps the blast radius of a bad estimate and is the direct mechanism against a price war: a competitor's price drop can't trigger an unbounded response.
- Explanations attached to every price shown. A price with a stated reason ("prices are higher right now due to high demand near you") measurably changes perceived fairness versus an unexplained number, even when the price itself is identical; this is a UX decision baked into the architecture, not an afterthought.
- Explicit fairness and price-war monitoring as first-class signals, not just revenue and conversion: tracking guest/rider complaint rate, cancellation rate, and competitor price volatility, and feeding those back into the optimizer's constraints, not just the dashboard.
Worked example
The elasticity number is what tells you whether raising or lowering price is even the right move, and the arithmetic is what makes that concrete rather than a hand-wave. Suppose a segment's baseline is a nightly price of $50 with a 20% booking conversion rate, and the estimated price elasticity of demand is ε=−1.5 (elastic: demand is more sensitive than price, in percentage terms).
Δ%demand=ε×Δ%priceBaseline revenue per look: 0.20×$50=$10.00.
| Scenario | New price | % demand change | New conversion | New revenue/look |
|---|---|---|---|---|
| +5% price | $52.50 | −7.5% | 18.5% | $9.71 |
| -5% price | $47.50 | +7.5% | 21.5% | $10.21 |
Both scenarios follow directly from the elasticity formula. The takeaway: because ∣ε∣>1 (demand is elastic at this price point), raising price actually loses revenue here (the percentage drop in bookings outweighs the percentage gain in price), while a small price cut gains revenue. This is exactly why the system needs a real elasticity estimate per segment rather than a rule like "raise price when demand is high": at an elastic point, raising price is the wrong move even though demand looks strong, and getting this backwards is what produces prices that feel punitive without even maximizing revenue.
Trade-offs & pitfalls
- The most common wrong turn is conflating "demand is high" with "price should go up": without an elasticity estimate, that intuition can push price past the revenue-maximizing point and straight into "feels like gouging" territory, especially in segments where demand is elastic.
- Unconstrained optimization (letting the model chase its point-estimate optimum every cycle) is what produces a price war: two systems reacting to each other's price changes in real time can oscillate or race to the bottom. Capping the rate and magnitude of price change per time window is the practical fix, at the cost of some short-term revenue left on the table.
- Cold-start segments (a new listing, a new city) have no reliable elasticity estimate yet; the system needs a conservative prior (borrowed from similar segments via hierarchical pooling) rather than defaulting to aggressive exploration on thin data.
- Explainability adds real engineering cost (the price optimizer's internal representation has to be translatable into a plain-language reason), and it's tempting to skip under launch pressure; the fairness perception cost of not doing it is real but harder to see on a dashboard than a revenue number, which is exactly why it gets cut first if it isn't treated as a first-class requirement.
- The absorb note for this question flags ride-hailing ETA-plus-surge-pricing prediction as a closely related demand-signal variant: the same architecture applies, but the demand signal there is driver/vehicle supply density and trip-completion ETA rather than booking-conversion rate, and the elasticity estimate is on ride-request abandonment rather than nightly-rate conversion. The core mechanism (probabilistic demand forecast, causal elasticity, bounded exploration, explained pricing) transfers directly; only the specific signals feeding it change.
Unlock Full Question Bank
Get access to all End-to-End ML System Design interview questions and detailed answers.
Sign in to ContinueJoin thousands of developers preparing for their dream job.