ML system design: the framework, recommendation, search and ads
Before this lesson: lesson 09 (precision, recall, AUC, calibration and the A/B test sample size). Lesson 12 and lesson 13 give more back-of-envelope practice (Little's law, latency percentiles); they are useful but not required. For depth: the in-batch softmax loss is the InfoNCE loss of deep learning lesson 14; PSI is built from the KL divergence of information theory lesson 3; deployment and monitoring are in MLOps lesson 6, MLOps lesson 7 and MLOps lesson 8. Time: about 110 minutes, plus the notebook and one timed mock design.
Why this matters #
ML system design is a standard round for ML engineers and research engineers, and some research scientist loops include it too. The prompt is short: "Design a feed", "Design product search", "Design ad click prediction". The interviewer wants to see a complete system with numbers, not a favourite model.
Strong researchers often fail this round in one typical way: they spend 30 minutes on the model architecture and never reach metrics, serving numbers or monitoring. A fixed framework and a few back-of-envelope formulas prevent this.
The same skills matter in ML for science. Before a forecast model runs every day, someone must define the target metric, the data pipeline, the time budget of one forecast cycle, the evaluation against baselines, and the monitor that notices when an input instrument changes.
1. Predict first #
2. The picture #
2.1 The funnel #
Start with the simplest case. A shop sells 1,000 products. For each visitor, one model scores all 1,000 products and the page shows the top 10. One model and one stage are enough.
Now grow the catalog to 50 M videos and the traffic to thousands of requests per second. Scoring every item with a rich model costs more than any data center has (question 1). The solution is a funnel: each stage sees fewer items and can spend more compute on each one.
- Retrieval (also called candidate generation) picks a few hundred candidates from the whole catalog. It must not miss good items, so it optimizes recall: the share of good items that it finds. It is cheap per item, because the item side is computed in advance.
- Ranking orders the few hundred candidates. It must put the best items first, so it optimizes precision at the top. It can afford expensive features, such as the user's past interactions with this exact creator.
- Re-ranking applies what a score per item cannot express: diversity across the page, freshness quotas, business and policy rules.
Move the sliders and watch the compute of each stage. With the defaults, the funnel needs a few GPUs, while ranking every item for every request needs hundreds of thousands (orange bar). Set the search to exact scan: retrieval without an approximate index becomes the most expensive stage. Raise the candidates : the ranker cost grows linearly with .
2.2 The framework is a funnel for your 45 minutes #
The same idea applies to your time. First come the cheap parts with high value: the goal, the metrics, the numbers and a complete sketch. Then come the details, where the interviewer wants depth. A design that is complete and shallow at minute 27 can still become deep. A design that is deep in one corner at minute 40 cannot become complete.
A recommendation system has a retrieval stage (50 M items → 500 candidates) and a ranking stage (500 → 20 shown). Which metric should the retrieval stage optimize?
An item that retrieval misses can never be shown, whatever the ranker does. So retrieval must keep the good items in its 500 candidates (recall@500). Ordering them well is the ranker's job (precision at the top, NDCG). Calibration matters for the final scores, for example in ads, not for the candidate set.
3. Definitions and main results #
3.1 The 45-minute framework #
| Minutes | Step | What you say or write |
|---|---|---|
| 0–5 | Clarify | users, surface, scale (daily active users, catalog, requests), latency limit, what exists today, privacy or policy limits. Write the numbers down. |
| 5–8 | Frame and metrics | business objective → online metric and guardrails → ML task (what is predicted, for whom, from what) → offline metric per stage |
| 8–11 | High-level design | the funnel with sizes, and the back-of-envelope numbers of 3.3 |
| 11–19 | Data, labels, features | data sources and logs; the label of each stage and its biases; negatives; 6–10 features with a reason each |
| 19–27 | Model and training | a baseline first; the model and loss of each stage; split by time; how often to retrain |
| 27–34 | Serving and evaluation | latency budget, caching, precomputation; offline evaluation; the A/B test (unit, size, duration, guardrails) |
| 34–45 | Monitoring, deep dives, trade-offs | what you monitor and alert on; position bias, cold start, feedback loops; the two or three main trade-offs and the next steps |
Three rules make the framework work:
- Numbers before boxes. State assumptions as numbers ("say 100 M daily users, 5 page loads each"). Then let the numbers choose the design.
- Breadth first, then depth. Reach a complete design by about minute 27. Then go deep where the interviewer points.
- Every choice with its alternative. "A neural ranker, because it learns from id embeddings and the watch history; gradient-boosted decision trees (GBDT) are the baseline."
This time plan is close to the public "delivery framework" of Hello Interview and to the seven-step framework of Xu and Aminian (see section 11).
3.2 Metrics at four levels #
| Level | Example (video home page) | Purpose |
|---|---|---|
| Business objective | long-term satisfaction, retention | what the company wants; rarely measurable directly |
| Online metric | watch time per user per day; 7-day return rate | decides the A/B test |
| Guardrails | p99 latency, "not interested" rate, creator diversity, crashes | must not get worse |
| Offline metrics | retrieval recall@K; ranking AUC, NE, NDCG@k, calibration | fast iteration before an A/B test |
Here p99 latency is the 99th percentile: 99% of requests are faster. Compute every offline metric on a time period after the training data. The definitions:
- Recall@K (retrieval) = (number of relevant items among the candidates) / (number of relevant items), averaged over users. The relevant items are those the user engaged with in the evaluation period.
- NDCG@k (normalized discounted cumulative gain; ranking with graded labels). Let be the label of the item at position . Then
where IDCG@k (the ideal DCG) is the DCG of the best possible order. NDCG is in . The discount rewards good items near the top. Some libraries (scikit-learn among them) use the linear gain instead of ; say which one you use.
- MRR (mean reciprocal rank; search) = the mean over queries of .
- AUC = the probability that a random positive gets a higher score than a random negative. It ignores calibration. A page only orders the items of one request, so the per-user AUC (averaged over users) is closer to what matters than one global AUC.
- Normalized entropy (NE) (He et al., 2014): the average log loss divided by the entropy of the background rate (the mean label):
NE means better than always predicting ; lower is better. Example: gives a denominator of 0.1985 nats, so a model with log loss 0.170 has NE = 0.856.
- Calibration ratio = (sum of predicted probabilities) / (number of observed positives). The target is 1. Ads need it (section 3.9).
Reorder the list with the arrows and watch DCG, NDCG and recall. The bars show what each position adds to DCG: this order against the best order (grey). Swap the top two items: the change is large. Swap the last two: the change is small, because of the discount . Switch the gain to "rel" and compare NDCG with the exponential gain.
A ranked list has graded labels in positions 1, 2, 3. With gain , what is NDCG@3?
DCG . The best order is : IDCG . NDCG . It is not 1: the item with label 1 sits at position 3 instead of 2, where the discount is smaller. It is close to 1 because the top item, which has by far the largest gain, is in the right place.
3.3 Back-of-envelope formulas #
Write for the daily active users, for the requests per user per day, and for the peak factor (peak traffic divided by average traffic).
- Queries per second (QPS). Average QPS (a day has 86,400 seconds); peak QPS average. A day is about s, so requests per day are about 1,160 QPS.
- Index memory for items, embedding dimension and bytes per number (4 for float32, 2 for float16). An HNSW index (hierarchical navigable small world: a layered graph of near neighbours) adds about neighbour ids of 4 bytes per vector, where is the graph degree. Product quantization (PQ) splits each vector into sub-vectors and stores each one as a 1-byte cluster id (256 centroids per sub-space): bytes per vector instead of . IVF-PQ first assigns each vector to a coarse cluster (an inverted file) and searches only a few clusters.
- Ranker load peak QPS scorings per second, for candidates per request. Ranker compute load FLOP/s for a model with dense parameters (one multiply and one add per parameter).
- Log volume requests per day items shown per request bytes per logged row.
For GPU counts we assume one GPU sustains 30 TFLOP/s on a ranking model. This is about 3% of the dense bf16 peak of an H100 (989 TFLOP/s): small batched MLPs and embedding lookups are limited by memory traffic, not by arithmetic. State such an assumption aloud in the interview.
3.4 Retrieval: several sources and a two-tower model #
Retrieval runs several sources in parallel and merges them: a personalized two-tower model, item-to-item co-engagement ("users who watched your last video also watched …"), subscriptions, and popular or fresh items per language and region. Several sources make the system robust and help with cold start. Each source returns 100–300 items. The merge removes duplicates and filtered items (already watched, blocked, against policy).
Two-tower model. A user tower embeds the user and the context (watch history, searches, country, device, time). An item tower embeds the item (an id embedding plus content features). Here are the weights of the towers and is the embedding dimension. The score is a dot product of normalized embeddings with a temperature :
The cosine is in , so the logits are in : a small allows sharp softmax distributions, a large forces flat ones. The score factorizes into a user part and an item part. So all item vectors are computed offline and stored in an approximate nearest neighbour (ANN) index, such as HNSW or IVF-PQ. At request time only the user tower runs, and the ANN search returns the top items in a few milliseconds. The price: the model cannot use cross features, that is, features of the (user, item) pair, such as "how often this user watched this creator". Cross features are the ranker's job.
Training with in-batch negatives. A batch holds positive pairs : a user and an item the user engaged with. For user , the other items of the batch act as negatives:
This is the InfoNCE (CLIP) loss with one correction, . Here is the probability that a random pair from the training stream has item . It grows with popularity, and it is estimated from the stream (Yi et al., 2019, use a streaming frequency estimate).
Result 1 (log-Q correction; Yi et al., 2019, §3). Without the correction, the in-batch softmax punishes popular items too much, because they are negatives in many batches. With the plain logits , the learned score tends to (plus a constant per user), so popular items rank too low. Subtracting from every logit removes this sampling bias.
Why (importance sampling).
- The full softmax needs , a sum over 50 M items. That is too expensive for every training example.
- The in-batch items are draws from the training stream: item comes with probability .
- For independent draws : . So is an unbiased estimate of : the corrected logits give the right normalizer.
- The plain batch sum estimates instead: a popularity-weighted sum. Training then makes play the role of the corrected logit, so .
- Constant factors such as shift every logit by the same amount and cancel inside the softmax.
Compare the two training losses. Users (violet arrows) and items (aqua dots, larger = more popular) live on a circle; a user likes nearby items and popular items. The figure trains the scores with SGD on the in-batch softmax. With the plain loss, the learned probabilities (orange) are too low for the popular items 1 and 2 and too high for rare ones. Switch on the log-Q correction: the learned bars (blue) match the true preferences (grey). Set the skew to 0 (all items equally popular): both losses agree.
ANN in numbers. Exact search costs multiply-adds per query: . At 11,600 QPS and 2 FLOPs per multiply-add, this is about FLOP/s only to find candidates. An ANN index visits a small fraction of the vectors and, at a tuned setting, returns most of the exact top in a few milliseconds on CPUs.
3.5 Ranking: labels, negatives and the multi-task model #
Labels and negatives. The ranker learns from impressions: an item shown to a user, with what happened next. Positives are engagements. Negatives are impressions without the engagement. Do not use random catalog items as ranking negatives: at serving time the ranker must separate good candidates from almost-good ones, and only impressions look like that.
A click alone is a poor label, because it rewards clickbait: titles that attract clicks and are then skipped. Use one multi-task model with several heads on shared layers (a shared bottom, or a multi-gate mixture of experts): , expected watch time, and . The final score is a fixed formula, for example
with weights tuned by A/B tests.
Result 2 (weighted logistic regression predicts expected watch time; Covington et al., 2016, §4.2). Train logistic regression with each clicked impression weighted by its watch time , and each non-clicked impression weighted by 1. Then the learned odds approximate the expected watch time per impression , where without a click.
Proof (for one cell of identical feature vectors , so the model is one logit ).
- Take impressions in the cell; of them are clicked, with watch times . The weighted log loss is , with .
- Use and : .
- Set : .
- With the click rate and : .
- Click rates on a home page are small (a few percent), so . At serving time, use as the predicted watch time.
The identity of step 3 is exact when the model has one free parameter per cell, as with one-hot video ids (section 5 checks it). A model with rich features applies the same idea across cells.
Features. Four groups, each with a reason:
- user: watch history (the last 50 video ids as embeddings, averaged or attended), language, country, account age — long-term taste;
- item: video id embedding, content embedding (title, thumbnail, audio, from a pretrained encoder), duration, upload age, creator id, engagement rates over the last hour, day and week — quality and freshness;
- user × item (cross): the user's past watches of this creator, time since the user last watched this topic, how often this video was already shown to this user without a click — often the strongest features;
- context: time of day, day of week, device, network — the same user wants short videos on a phone at 8 am and long ones on a TV at 9 pm.
Ids become learned embeddings (they hold most of the parameters). Counts get a log or quantile transform. Rates are smoothed with a prior, because a video with few impressions has a noisy rate. The display position is a training-only feature (section 3.7).
Split by time. Train on days 1–28, validate on day 29, test on day 30. A random split leaks the future (the same trending video appears on both sides) and makes offline gains look larger.
Why should the ranker's negatives be impressions without engagement, and not random items from the catalog?
The ranker orders about 500 candidates that are all plausible. Random catalog items are easy to reject, so a ranker trained on them learns little about separating good from almost-good candidates. Impressions without engagement are exactly the hard cases. They are not "truly irrelevant" either (the user may not have seen the item), which is one reason for the position-bias corrections of section 3.7.
3.6 Online evaluation #
An offline gain is a hypothesis; the A/B test decides. Covington et al. (§2) report that offline metrics and live A/B results do not always agree. Randomize by user, not by request: each user then sees one system, and the per-user metrics of the two arms are independent. For a per-user metric with standard deviation and a minimum detectable effect (absolute), the sample size per arm at two-sided level and power is (lesson 09)
where is the -quantile of the standard normal. For and power 0.8, . Run full weeks (at least two) to cover weekly cycles and novelty effects. Check for sample-ratio mismatch, and fix the decision rule before the start.
Two tools that ranking teams use:
- CUPED (controlled experiments using pre-experiment data). Subtract the part of the metric that the same user's pre-experiment value predicts. With correlation between the two periods, the variance, and so , falls by the factor .
- Interleaving. Merge the lists of the two rankers into one page per user, and count which ranker's items get the engagements. Each user compares both rankers, so this is much more sensitive than an A/B test.
Team-draft interleaving
Team-draft interleaving (Radlinski et al., 2008) builds the page like a sports draft. In each round, a coin decides which ranker picks first; each ranker adds its highest-ranked item that is not yet on the page, and the item is credited to that ranker. A click credits the ranker that contributed the item. Over many users, the ranker with more credited clicks is preferred. Interleaving measures preference, not watch time or retention, so use it to filter candidate rankers quickly, and confirm the winner with an A/B test.
3.7 Deep dives you must be ready for #
- Position bias. Items at the top get more clicks, whatever their quality, because users look
at the top more often. A model trained on raw clicks learns "what the old system showed at the
top". In the position-based click model, :
the user examines position with probability (the propensity) and clicks an
examined item with probability (its relevance). Fixes:
- add the position as a training feature and set it to one fixed value at serving time;
- or weight each click by the inverse propensity (IPS, inverse propensity scoring). Then at every position, so the weighted click rate estimates the relevance without bias. The propensities are estimated from a small slice of traffic in which the order is randomized.
Simulate a click log. The old ranker almost always shows items in the same order (A first, E last). Compare the true relevance, the naive click rate and the IPS estimate. The naive rate calls A the best item; IPS finds E. Set the position bias η to 0: both estimates agree. Switch to random order: the naive rate keeps the right order but is too small for every item. Try 2,000 sessions: IPS is unbiased but noisy, most of all for items that sit low on the page.
- Feedback loops. The model gets labels only for what it showed, so it reinforces its own past choices: popular items get more popular. Fixes: a small exploration budget (for example 1–5% of the slots for less-shown items), logged propensities for unbiased offline evaluation, and diversity rules.
- Cold start. A new video: the item tower uses content features, so a new video gets an embedding at upload time; a freshness pool and exploration slots collect its first signals. A new user: onboarding topics, popular items in the user's language and region, and context features (device, country, time).
- Training–serving skew. A feature computed one way offline and another way online breaks the model without any error message. Fixes: log the features at serving time and train on these logs; use one feature definition for both paths (a feature store with point-in-time correct joins, so a training row sees only the feature values that existed at that time).
- Delayed labels. Some labels arrive late: a purchase days after the click, a subscription cancelled weeks later. Training on recent data then sees too few positives. Fixes: wait a fixed attribution window before a row becomes a negative, or model the delay; for monitoring, use fast proxies (clicks, add-to-cart) and check them against the slow label later.
- Freshness. Behaviour changes every day. He et al. (2014, §3.2) found that training daily instead of weekly lowered NE by about 1%. Retrain the ranker daily or continuously, and add new uploads to the index within minutes.
3.8 Deployment and monitoring #
From a trained model to users. A new model passes through these steps, in this order:
- Promotion gate. A written rule, checked by code on a frozen test set, candidate against
production. For example: the overall metric drops by at most 0.5 points; no slice with at least
examples drops by more than 2 points; latency at the expected peak load stays within
the target; the behavioural tests pass. A model registry stores the versions with their
metrics and lineage, and an alias (
champion) points to the version in production. - Shadow deployment. The new model receives a copy of the real traffic, but its answers are not shown to users. You compare its predictions, latency and errors with production. It sees real traffic with no user impact.
- Canary release. A small share of users (for example 1%, then 5%, then 25%) gets the new model. Guardrails (errors, latency, the online metric) are watched at each step, and the rollout stops automatically if one of them gets worse.
- A/B test for the decision on the product metric (section 3.6).
- Rollback. Move the registry alias back to the previous version. Keep the previous model loaded or ready, so a rollback takes minutes, not a new training run.
What to monitor. Four layers:
- system: QPS, p50 and p99 latency per stage, errors, timeouts per retrieval source;
- data: feature null rates, freshness of streaming features, drift of the input distributions, the share of new items in the index;
- model: prediction drift, calibration ratio, NE on yesterday's data (when labels arrive), the share of each retrieval source in the final page;
- business: the online metric and the guardrails.
Drift statistics. Input drift (also called covariate shift) and prediction drift need no labels, so they are the earliest signals. The two standard statistics:
- PSI (population stability index). Bin the feature with the quantiles of the reference (training) data into bins. Let be the reference share and the live share in bin . Then . A common rule of thumb (from credit scoring): below 0.1 stable, 0.1–0.25 moderate shift, above 0.25 large shift.
- KS (two-sample Kolmogorov–Smirnov): over the empirical cumulative distribution functions, with a p-value. With thousands of samples, tiny and harmless shifts become "significant". Use as an effect size, or PSI with a threshold, and the p-value only as one input.
Result 3 (PSI is a symmetrized KL divergence). .
Proof. and . Add the two: .
So PSI , it is 0 only when , and it is symmetric. Each term is , because and have the same sign. An empty bin makes it infinite, so implementations floor the shares at a small .
Result 4 (the noise floor of PSI). With no drift, a reference of samples and a live window of samples give, for large and ,
Proof.
- Write with a small . Then , so each term (higher-order terms dropped).
- So , the chi-square divergence.
- With no drift, both shares estimate the same probability from independent samples, with variances and . So .
- Divide by and sum: , because .
With bins and , the floor is already , close to the 0.1 threshold, with no drift at all. A simulation shows that 37% of such windows exceed 0.1. So choose the window so that the floor is far below the alert threshold, and alert with hysteresis: raise the alert above 0.25, and clear it only below 0.1. Then one noisy window does not switch the alert on and off.
Keep the shift at 0 and look at the violet histogram: the PSI of 400 windows without drift. With , more than a third of them cross 0.1. Raise the window size and set : the whole histogram moves towards 0. Then add a small true shift (0.2σ) and compare the PSI of this window (orange line) with the no-drift histogram. With large samples, the KS p-value is tiny even for small shifts.
Your monitor compares 200 live requests with 200 training rows, using PSI with 10 bins, and reports PSI = 0.11. Is this good evidence of drift?
PSI on finite samples is never exactly 0; its noise floor is . Here it is 0.09, and by simulation about 37% of no-drift windows exceed 0.1. Use larger windows or fewer bins, compare with the no-drift distribution, and alert with hysteresis. PSI is not limited to mean shifts: any change of the binned shares raises it.
A small MLOps project to show in interviews
Interviewers often ask "walk me through a model you deployed". A small public repository answers this better than a CV line. Keep the model simple and make each part answer one question:
| Part | Minimal version | Common tool | Question it answers |
|---|---|---|---|
| data versioning | a content hash per data file, a hash-based split | DVC | Which exact bytes trained this model? |
| data validation | schema and range checks | pandera or own code | Does bad data stop before training? |
| tracked training | one config file, one tracked run | MLflow | Which code, data and config gave this number? |
| tests | unit, data, model, API | pytest | Does a change break something? |
| registry and gate | versions, a champion alias, a written gate |
MLflow registry | Why is this version in production? |
| serving | a web API that loads the model once | FastAPI | Does it answer within the latency target? |
| container | a multi-stage image | Docker | Does it run the same everywhere? |
| CI | lint, tests, image build, smoke test | GitHub Actions | Is every change checked? |
| monitoring | PSI and KS on inputs and predictions, with hysteresis | own code | Do we notice when the data change? |
A layout that works:
project/
pyproject.toml, uv.lock pinned environment
configs/*.yaml every hyperparameter (a tiny config for CI)
data/raw/*.dvc pointers to versioned data (the data is not in git)
src/proj/ data.py train.py evaluate.py gate.py serve.py monitor.py
tests/unit tests/data tests/model tests/api
Dockerfile, .github/workflows/ci.yml, README.md (diagram, numbers, how to run)
In the walkthrough, give the problem and users (30 s), the architecture (60 s), two or three decisions with their alternatives (90 s), the numbers (accuracy per slice, p95 latency at peak load, drift thresholds and the noise floor; 45 s), and what would change at 10 times the traffic. The MLOps track teaches each part.
3.9 Search and ads: what changes #
| Recommendations | Search | Ads | |
|---|---|---|---|
| Input | user + context | query + user + context | user + context (+ query for search ads) |
| Retrieval | two-tower, co-engagement, subscriptions, fresh pool | lexical (BM25, a term-matching score on an inverted index) + semantic (query and document towers) | ads that match the targeting and have budget left, then a two-tower model |
| Ranking label | watch time, likes, dismissals | graded relevance from human raters, plus clicks and purchases | click and conversion, predicted as pCTR and pCVR |
| Final order | multi-task score + re-ranking | relevance, then personalization | an auction on bid × pCTR (× pCVR), plus a quality term |
| Offline metric | recall@K, NDCG@k, AUC | NDCG@10, MRR | NE, calibration ratio, AUC |
| Special issues | diversity, feedback loops | query understanding (spelling, intent), zero-result rate | calibration, delayed conversions, budget pacing, new advertisers |
Here pCTR and pCVR are the predicted click-through rate and conversion rate. Budget pacing spreads an advertiser's daily budget over the day, so it is not spent in the first hours.
In ads the predicted probability sets a price, so it must be calibrated, not only well ordered. Click rates are often below 1%, so teams keep all positives and only a fraction of the negatives (negative downsampling). This changes the base rate, and the predictions must be corrected.
Result 5 (recalibration after negative downsampling; He et al., 2014, §6.3). Assume the training data keep every positive and keep each negative with probability , independently of the features. Let be a calibrated prediction on these sampled data. The probability on real traffic is
Proof.
- Downsampling keeps every positive and a fraction of the negatives, independently of the features . So for each , the odds in the sampled data are .
- Solve for the true odds: .
- Turn the odds into a probability: .
Example: a true click rate of 0.1% with becomes about 9.1% in the sampled data; the formula maps 9.1% back to 0.1%.
Answers to section 1. (1) Scoring every video costs FLOP/s: about 386,000 GPUs at 30 TFLOP/s each. Retrieval cuts the scored set to about 500 items per request, a factor of , and the ranker then needs about 4 GPUs. (2) Possible reasons: the label (click) is not the objective (watch time), so a better click model can promote clickbait; the logged data come from the old system (position bias, feedback loop), so a gain on logged data need not transfer; training–serving skew (a feature has other values online); the ranker only reorders the same 500 candidates, so retrieval limits the gain; the test has too little power for the true effect.
4. Worked example: home-page video recommendations #
Prompt: "Design the recommendations on the home page of a video platform." We follow the time plan of section 3.1. All numbers are an invented but realistic example.
Clarify (0–5 min). You ask; the interviewer answers; you write the answers down.
| Question | Answer |
|---|---|
| Users and traffic | 100 M daily active users, 5 home-page loads per user per day, peak = 2 × average |
| Catalog | 50 M videos; 500 k new uploads per day |
| Page | 20 videos per load |
| Latency | p99 ≤ 200 ms for the whole call |
| Signals | impressions, clicks, watch time, likes, shares, dismissals, subscriptions |
| Today | a popularity list per country |
Frame and metrics (5–8 min). Business objective: users find videos they value and come back. Online metric: watch time per user per day; secondary: 7-day return rate. Guardrails: p99 latency, dismiss rate, creator diversity (distinct creators in the top 10), crash rate. ML task: for a (user, context) pair, retrieve about 500 candidates from 50 M; for each candidate, predict , expected watch time, and ; order by the score of section 3.5. Offline metrics: recall@500 for retrieval; AUC and NE per head, and NDCG@20, for ranking.
Numbers (8–11 min).
- Requests: per day, so QPS on average and 11,574 QPS at peak.
- Retrieval index with : B GB in float32, 12.8 GB in float16, 38.4 GB as HNSW (: extra bytes per vector, float32), 1.6 GB with 32-byte PQ codes. Each option fits in the memory of one server: replicate the index for throughput, no sharding.
- Ranker: M scorings per second at peak. With dense parameters: FLOP/s, about 4 GPUs at 30 TFLOP/s. Provision about twice that per region, for headroom and failures.
- Embedding tables dominate memory: the video ids alone take B GB (64-dimensional, float32).
- Logs: impressions per day. With a 5% click rate, keeping all clicks and 10% of the non-clicks gives training rows per day, about 0.7 TB at 500 bytes per row. Training compute is FLOPs per day: under one GPU-hour at 30 TFLOP/s. The cost of training a recommender lies in data loading and embedding lookups, not in FLOPs.
Latency budget (p99 ≤ 200 ms).
| Stage | Budget |
|---|---|
| request parsing and user features | 10 ms |
| retrieval: 4 sources in parallel, merge and filters | 20 ms |
| feature fetch for 500 candidates (batched key-value reads) | 20 ms |
| ranking: one batched GPU call | 30 ms |
| re-ranking and policy filters | 10 ms |
| network and serialization | 20 ms |
| total | 110 ms, which leaves 90 ms for the tail |
The slack matters because parallel calls make the tail worse. If a request waits for 10 calls, and each call is slower than its own p99 in 1% of the cases (independently), then at least one call is slow with probability . Give each source a timeout (answer without a slow source), and send a duplicate (hedged) request to a second replica when the first one is late.
Data and labels (11–19 min).
- Retrieval: positives are meaningful watches (for example, at least half of the video or at least 30 s). The input is the history before the watch, and the label is the next watch; predicting a randomly held-out watch leaks future information (Covington §3.4). Negatives: in-batch, with the log-Q correction. Use a fixed number of examples per user, so that heavy users do not dominate the loss.
- Ranking: one row per impression, with the features logged at serving time. One label per head: click, watch time, like, dismiss. Sampling: keep every clicked impression and 10% of the non-clicked ones.
- Features: the four groups of section 3.5, and the position for training only.
Model and training (19–27 min).
- Baselines: for retrieval, popular videos per country plus co-watch; for ranking, a GBDT on about 20 features that predicts . Every later model must beat these online.
- Retrieval: a two-tower model; the user tower reads the last 50 watches and the context; the item tower reads the id and the content embedding; in-batch softmax with the log-Q correction; . Retrain weekly. Embed new uploads at upload time and add them to the index every 15 minutes.
- Ranking: a multi-task neural network (shared embeddings, a mixture-of-experts bottom, four heads). Binary cross-entropy for click, like and dismiss; weighted logistic regression for watch time (Result 2). The training rows keep only 10% of the non-clicks (), so correct the heads before they are combined. Click head: Result 5. Watch-time head: multiply the learned odds by (its weight-1 rows are exactly the non-clicks). Like and dismiss heads: Result 5 does not apply, because their negatives include every clicked row but only 10% of the non-clicked rows, and most dismissals are non-clicks. Give each kept non-click row the weight in these two losses instead; this restores the true data distribution. Retrain daily, starting from yesterday's weights.
- Re-ranking: at most 2 videos per creator in the top 10; 2 of the 20 slots for fresh or exploration videos; policy filters.
- Split by time: train on 28 days, validate on day 29, test on day 30.
Evaluation (27–34 min).
- Offline retrieval, on day 30: a user watched 4 videos that day, and 3 of them are among the 500 candidates, so recall@500 = 3/4 = 0.75 for this user. Average over users.
- Offline ranking: graded labels 0 (shown, not clicked), 1 (clicked, short watch), 2 (watched more than half), 3 (liked). A page in model order has the labels :
The best order gives , so NDCG@5 .
- Online: an A/B test with the user as the unit. In past data, the per-user average daily watch time over two weeks has mean 40 min and min (a heavy tail). The team wants to detect +0.5%, that is, min:
1.4% of the daily users per arm. Run 14 days for weekly cycles and novelty. With CUPED and a pre-period correlation : M per arm. Ship if the primary metric improves at and no guardrail gets worse.
Deployment, monitoring, trade-offs (34–45 min). The new ranker passes the promotion gate, runs in shadow for two days (compare score distributions and latency), then goes to a 1% canary and then to the A/B test. Dashboards on the four layers of section 3.8, with alerts: p99 latency above 200 ms; any retrieval source timing out in more than 1% of requests; a feature null rate that doubles; a calibration ratio outside ; PSI of key features above 0.25 on windows of at least 10,000 requests (noise floor with reference rows and 10 bins); a drop in the share of new videos in the index (a broken embedding job removes fresh candidates without any error). Then the deep dives of section 3.7 that the interviewer picks. Trade-offs to state: the speed of the two-tower model against the power of cross features; the cost of daily retraining against freshness; short-term engagement against long-term satisfaction (survey answers as a label for the second).
A request waits for 20 index shards. Each shard is slower than 15 ms in 1% of cases, independently. What fraction of requests waits more than 15 ms?
The request is fast only if all 20 shards are fast: . So of requests are slow. It is not 20% ( counts some requests twice) and not 1%: fan-out turns a rare slow call into a common slow request. Fixes: replicate instead of shard when the index fits in one server, timeouts, hedged requests.
5. In code #
The numbers of section 4, NDCG with scikit-learn, Result 2 on simulated logs, the A/B sample size and the PSI noise floor:
import numpy as np
from scipy.stats import norm
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import ndcg_score
# (a) Back-of-envelope numbers of section 4
DAU, loads, peak_factor = 100e6, 5, 2
N_items, d, K, slate, P_dense = 50e6, 128, 500, 20, 10e6
qps_peak = peak_factor * DAU * loads / 86_400
print(f"peak QPS {qps_peak:,.0f}") # 11,574
print(f"index fp32 {N_items * d * 4 / 1e9:.1f} GB, PQ-32 {N_items * 32 / 1e9:.1f} GB")
ranker = qps_peak * K * 2 * P_dense # FLOP/s of the ranker
print(f"ranker {ranker:.2e} FLOP/s = {ranker / 30e12:.1f} GPUs at 30 TFLOP/s")
print(f"rank all items: {qps_peak * N_items * 2 * P_dense / 30e12:,.0f} GPUs")
# index fp32 25.6 GB, PQ-32 1.6 GB
# ranker 1.16e+14 FLOP/s = 3.9 GPUs at 30 TFLOP/s
# rank all items: 385,802 GPUs
# (b) NDCG@5; scikit-learn uses the label itself as the gain, so pass 2**rel - 1
rel = np.array([[2, 0, 3, 0, 1]]) # labels in the model's order
score = np.array([[5, 4, 3, 2, 1]]) # higher score = earlier position
print(round(ndcg_score(2**rel - 1, score, k=5), 4), round(ndcg_score(rel, score, k=5), 4))
# 0.7332 0.8162 (exponential gain, linear gain: two different numbers)
# (c) Weighted logistic regression learns odds = sum(T) / (N - k), close to E[T]
rng = np.random.default_rng(0)
n = 200_000
videos = {"A (long video)": (0.04, 8.0), "B (clickbait)": (0.10, 2.0)} # click rate, minutes per click
ids, y, w = [], [], []
for g, (p_click, minutes) in enumerate(videos.values()):
clicked = rng.random(n) < p_click
watch = np.where(clicked, rng.exponential(minutes, n), 0.0)
ids.append(np.full(n, g)); y.append(clicked); w.append(np.where(clicked, watch, 1.0))
ids, y, w = np.concatenate(ids), np.concatenate(y), np.concatenate(w)
X = np.eye(2)[ids] # one-hot video id
ctr = LogisticRegression(C=np.inf, fit_intercept=False, tol=1e-10).fit(X, y)
wlr = LogisticRegression(C=np.inf, fit_intercept=False, tol=1e-10).fit(X, y, sample_weight=w)
for g, name in enumerate(videos):
m = ids == g
print(f"{name}: CTR {ctr.predict_proba(np.eye(2)[[g]])[0, 1]:.3f}, "
f"odds {np.exp(wlr.coef_[0, g]):.3f}, sum(T)/(N-k) {w[m & y].sum() / (~y[m]).sum():.3f}")
# A (long video): CTR 0.039, odds 0.331, sum(T)/(N-k) 0.331
# B (clickbait): CTR 0.100, odds 0.220, sum(T)/(N-k) 0.220
# A click model ranks B first; the watch-time model ranks A first (E[T]: 0.32 vs 0.20).
# (d) A/B sample size per arm for watch time (sigma = 60 min, delta = 0.2 min)
z2 = (norm.ppf(0.975) + norm.ppf(0.8)) ** 2
print(f"n per arm {2 * z2 * 60**2 / 0.2**2:,.0f}") # 1,412,798
# (e) PSI noise floor: no drift, 10 equal bins, 200 vs 200 samples
k, n_ref, m_live = 10, 200, 200
a = rng.multinomial(n_ref, np.full(k, 1 / k), size=20_000) / n_ref
b = rng.multinomial(m_live, np.full(k, 1 / k), size=20_000) / m_live
a, b = np.clip(a, 1e-4, None), np.clip(b, 1e-4, None)
psi = np.sum((b - a) * np.log(b / a), axis=1)
print(f"mean PSI {psi.mean():.3f}, formula {(k - 1) * (1 / n_ref + 1 / m_live):.3f}, "
f"share above 0.1: {(psi > 0.1).mean():.2f}")
# mean PSI 0.092, formula 0.090, share above 0.1: 0.37
The notebook adds the in-batch softmax with and without the log-Q correction, and asks you to write each piece yourself.
6. Where it shows up #
- Interviews. Recommendations, feeds, search, ads, notifications and "people you may know" are all funnels with the template of section 4. Harmful-content and fraud detection use the same framework with a different funnel: cheap rules, then models, then human review.
- Industry papers. The YouTube funnel and weighted logistic regression (Covington et al., 2016), the two-tower model with the log-Q correction (Yi et al., 2019), and NE, calibration and downsampling for ads (He et al., 2014) are the standard references behind this lesson.
- ML for science. The funnel is the shape of many science pipelines. Virtual screening in drug discovery runs a fast filter or docking on millions of molecules and expensive simulations on a few hundred. Literature and dataset search for researchers is retrieval and ranking exactly as here. Analogue methods in weather and climate retrieve similar past atmospheric states, today often with learned embeddings and ANN search. Earthquake detection runs a cheap trigger (such as STA/LTA, the ratio of a short-term to a long-term average of the signal amplitude) before a slower phase picker. Drift monitoring applies when an instrument or an upstream model version changes.
7. Common mistakes #
- Starting with the model. The first 8 minutes are questions, metrics and numbers.
- No numbers. Without QPS, catalog size and a latency limit, no design choice can be defended. Ask for them, or state them as assumptions.
- Click as the only label. It rewards clickbait. Add watch time, likes and dismissals.
- Wrong negatives. Ranking negatives are impressions without engagement; random or in-batch items are for retrieval, and in-batch negatives need the log-Q correction.
- A random train/test split. It leaks the future; split by time.
- Uncalibrated probabilities where calibration matters. In ads the probability sets the price; in a multi-task score, heads trained on downsampled data must be corrected before they are combined.
- Offline gain = success. Only an A/B test with enough power and with guardrails decides.
- Randomizing by request when the metric is per user. Then each user sees both systems, and the per-user metric mixes them.
- Budgeting only the average latency. Parallel calls and heavy tails make p99 the real limit.
- A fixed PSI threshold on small windows. The noise floor can be close to the threshold; compute it before you choose the window.
- No plan for cold start, position bias, deployment or monitoring. These are the usual deep dives.
8. Check yourself #
- 20 M daily active users, 8 requests per user per day, peak factor 3. Average and peak QPS?
Answer
QPS on average; QPS at peak. - 200 M items with 64-dimensional float16 embeddings. Index memory? And with 16-byte PQ codes?
Answer
B GB; with PQ, B GB. - An ads model trained with negatives kept at predicts . What is the calibrated
click probability?
Answer
, about 1.2%. - Video A has a click rate of 4% and 8 minutes of mean watch time after a click; video B has 10% and
2 minutes. Which one does a click model rank first, and which one does the weighted logistic
regression of Result 2 rank first?
Answer
The click model ranks B first (). The weighted model learns odds mean watch: for A and for B, so it ranks A first. - bins, a reference of 10,000 rows, live windows of 500 requests. What is the PSI noise
floor? And with windows of 100? What does this mean for a 0.1 threshold?
Answer
; with , . A 0.1 threshold is safe for windows of 500 but gives many false alerts for windows of 100: use larger windows, fewer bins, or a higher threshold with hysteresis. - What is the difference between a shadow deployment and a canary release?
Answer
In a shadow deployment the new model gets a copy of real traffic, but users never see its answers: you check predictions, latency and errors with no user impact. In a canary release a small share of real users gets the new model's answers, so you can measure its effect on guardrails before a wider rollout, with an automatic stop and a rollback.
Name the first three steps of the 45-minute ML system design framework, with their minutes.
Clarify (0–5 min), frame and metrics (5–8 min), high-level design with the funnel and the numbers (8–11 min).
Why does a large recommender use a funnel instead of one ranking model?
Scoring every item with a rich model costs far too much (about FLOP/s for 50 M items at 11,600 QPS); retrieval cuts the scored set to a few hundred items.
Which metric does retrieval optimize, and which does ranking optimize?
Retrieval: recall (do not miss good items). Ranking: precision at the top (NDCG@k, per-user AUC).
Write DCG@k and NDCG@k.
; , where IDCG is the DCG of the best order.
Define normalized entropy (NE).
The average log loss divided by the entropy of the background rate : . NE beats always predicting .
Compute the peak QPS for 100 M daily users, 5 requests each, peak factor 2.
QPS.
Why does the in-batch softmax need the log-Q correction?
Popular items appear as negatives in many batches, so they are punished too much; the learned score tends to . Using logits removes this sampling bias.
What is training–serving skew, and how do you prevent it?
A feature computed differently offline and online. Log features at serving time and train on the logs, or use one feature definition for both (a feature store with point-in-time joins).
What is the noise floor of PSI with bins, reference and live samples?
with no drift; 0.09 for , .
Which deployment pattern sees real traffic with no user impact?
A shadow deployment: the new model gets a copy of the traffic, but its answers are not shown.
9. Interview angle #
Recognize the prompt.
| The prompt says | Start with |
|---|---|
| "recommend", "feed", "home page", "people you may know" | the funnel; retrieval sources + two-tower; multi-task ranker; re-ranking |
| "search", "query", "find products" | query understanding; BM25 + semantic retrieval; relevance labels from raters; NDCG@10, MRR |
| "ads", "bid", "click prediction" | calibration, NE, negative downsampling and Result 5; delayed conversions; auction on bid × pCTR |
| "harmful content", "fraud", "spam" | a precision–recall trade-off at a threshold; cheap rules, then models, then human review |
| "offline better, online flat" | label ≠ objective, position bias, skew, retrieval limits, power |
| "how do you ship it?" | gate → shadow → canary → A/B test → rollback |
| "how do you know it still works?" | four monitoring layers; PSI and KS with the noise floor and hysteresis |
The routine to say aloud. "Let me first clarify the users, the scale and the latency limit. Then I define the metrics at four levels. Then I size the system with numbers, draw the funnel, and go through data, models, serving and evaluation. I leave the last ten minutes for monitoring, deep dives and trade-offs."
Typical questions and short model answers.
- "Why two stages?" — "Scoring 50 M items for each of about 11,600 requests per second with a -FLOP ranker needs about FLOP/s, hundreds of thousands of GPUs. So a cheap retrieval stage with precomputed item vectors and an ANN index picks about 500 candidates, and only these go to the ranker. Retrieval is judged by recall@500, the ranker by NDCG and per-user AUC."
- "How do you train the retrieval model?" — "A two-tower model with a dot-product score, so item vectors are precomputed and served from an HNSW or IVF-PQ index. I train it with an in-batch softmax: the other items of the batch are negatives. Popular items appear as negatives too often, so I subtract from each logit, where is the item's frequency in the stream. Cross features stay in the ranker."
- "Which label? Why not clicks?" — "Clicks reward clickbait. I train a multi-task ranker with click, watch time, like and dismiss heads, and combine them in a score whose weights I tune with A/B tests. For watch time, logistic regression with watch-time weights learns odds close to the expected watch time per impression."
- "Offline AUC went up, online watch time did not. Why?" — "The label may not be the objective; the logged data come from the old system, with position bias and a feedback loop; there may be training–serving skew; the ranker only reorders the same candidates; or the test had too little power. I would check each one, starting with skew and the label."
- "How do you ship the new model safely?" — "A written promotion gate on a frozen test set, including slices and latency. Then a shadow run on copied traffic, a canary at 1% and 5% with automatic stops on guardrails, and the A/B test with user randomization, a fixed sample size and at least two full weeks. Rollback is one alias move in the registry."
- "How do you detect drift without labels?" — "PSI and KS on the key inputs and on the predictions. PSI has a noise floor of , so I choose windows where it is far below the threshold, and alert with hysteresis. When labels arrive, I add calibration and NE on recent days. First I always rule out a pipeline bug: units, a new upstream source, a schema change."
Typical follow-ups. "How does a new video get recommended?" (content features in the item tower, a fresh pool, exploration slots). "What changes for ads?" (calibration, downsampling correction, delayed conversions, budget pacing). "Your p99 is too high." (timeouts per source, hedged requests, fewer candidates, a smaller ranker, caching). "How would you reduce the training cost?" (it is data loading and embedding lookups, not FLOPs: fewer negatives, feature caching, incremental daily training).
Mock prompt (search). "Design product search for an online shop: 10 M products, 2 M searches per day, p99 ≤ 300 ms." A strong answer contains: the numbers (about 23 QPS on average, about 70 with an assumed 3× peak; a 256-dimensional float32 index of 10.2 GB); query understanding (spelling, category intent); hybrid retrieval with BM25 and a two-tower semantic model, merged to about 1,000 candidates; a ranker on query–product features (BM25 score, embedding similarity, past click and add-to-cart rates for this query–product pair, price, stock); because the traffic is low, a small cross-encoder (a transformer that reads query and product together) on the top 100 (about FLOPs per pair for parameters and 128 tokens, so about FLOP/s at peak: one or two GPUs); labels from purchases, add-to-cart and clicks, plus human relevance grades for the test set; NDCG@10 offline; online search conversion, revenue per search and zero-result rate; position bias; new products through text and image embeddings; an A/B test by user.
10. Practice #
The notebook for this lesson runs in Colab and in the browser. Each exercise has a check cell and a folded solution:
ndcg_at_k: NDCG@k with both gains, checked againstsklearn.metrics.ndcg_score.back_of_envelope: QPS, index memory, ranker GPUs and log volume for any prompt.in_batch_softmax_loss: the loss with and without the log-Q correction; then train a score table on a stream with a popularity skew and see which one recovers the true preferences.psiand its noise floor: PSI from two samples with reference-quantile bins, and a simulation of the no-drift distribution.ab_duration: days needed for an A/B test from the traffic, the metric's and the minimum detectable effect, rounded up to full weeks.
Timed mock designs (45 minutes each, aloud, on paper; then check against the table of section 3.1: a complete design by minute 27, and a label, a metric and a number for every stage):
- the worked example of section 4, from memory;
- "Design ad click prediction for a social-media feed" (calibration, NE, downsampling, delayed conversions);
- the product-search mock prompt of section 9;
- "Design a notification system that decides which push notifications to send" (a funnel with a daily budget per user, and a guardrail on unsubscribes);
- "Design harmful-content detection for a video platform" (rules, models, human review; precision at a threshold).
Numbers drill. For each prompt above, compute QPS, index memory, ranker load and log volume in under 5 minutes, with rough arithmetic only.
Say it aloud drill. Answer each typical question of section 9 in 90 seconds, with a timer. Each answer needs one number. Record yourself and compare with the model answers.
11. Watch and read #
- Covington, Adams and Sargin (2016), Deep Neural Networks for YouTube Recommendations: §2 (the funnel), §3.4 (next-watch labels), §4.2 (weighted logistic regression).
- Yi et al. (2019), Sampling-Bias-Corrected Neural Modeling for Large Corpus Item Recommendations: §3 (in-batch softmax, the log-Q correction, streaming frequency estimation).
- He et al. (2014), Practical Lessons from Predicting Clicks on Ads at Facebook: §2 (NE and calibration), §3.2 (data freshness), §6 (negative downsampling and recalibration).
- Joachims, Swaminathan and Schnabel (2017), Unbiased Learning-to-Rank with Biased Feedback: the propensity-weighted estimator of section 3.7.
- Hello Interview, ML System Design in a Hurry: the delivery framework (free web pages): the time plan of section 3.1 and worked designs.
- Stanford CS 329S: Machine Learning Systems Design (Chip Huyen; free lecture notes and slides). Her book Designing Machine Learning Systems (O'Reilly, 2022) covers the same ground in depth.
- Goku Mohandas, Made With ML (free): versioning, testing, serving and
monitoring, with code; the project of the
::: deeperblock in section 3.8. - Xu and Aminian, Machine Learning System Design Interview (ByteByteGo, 2023; a book, not free): chapter 1 (a seven-step framework) and the chapters on video recommendation, ad click prediction and the news feed.
Runs in your browser with nothing to install. The notebook repeats the key demos and has exercises with checks; solutions are inside, folded.
No Google account needed: Python runs inside your browser. The first start downloads about 40 MB; your changes are saved in this browser only.