0% completed
Design a Recommendation System
On This Page
- Frame the Problem: The Scale Is the Design
- Define Success: Online Truth, Offline Proxies
- Map the Data: Impressions Are the Labels
- Pick the Model: One Model Per Funnel Stage
- The Serving Path: A Funnel That Ends in Rules
Deep dive: cold start, both kinds
- Keep It Working: The Loop and Its Traps
- Count the Costs: Precompute Heavy, Serve Cheap
- In the Interview
- Common Mistakes
- Same Design, Other Questions
- TL;DR
1. Frame the Problem: The Scale Is the Design
The question: "Design a recommendation system for a video platform." Start with the numbers, because the numbers decide everything else. There are 50 million videos. There are 100 million people using the app every day. The homepage has to load in about 200 ms, and it shows about 20 videos.
Now look at what those numbers rule out. You cannot run a good model over 50 million videos on every single request. It is roughly a million times too slow and too expensive. That one limit shapes every recommendation system ever built. The way around it has a name: the funnel.
Think of hiring. You do not interview every applicant. You skim resumes fast, interview a short list carefully, then make one final call. Recommendations work the same way. A cheap stage narrows millions down to hundreds. An expensive stage ranks those hundreds. A last stage applies business rules.
Say this framing out loud early: this is a prediction problem, the shape is a ranking funnel, and you are designing the homepage feed. Search and notifications are out of scope.
2. Define Success: Online Truth, Offline Proxies
You measure success at two levels, per the framework.
What the business actually cares about (online). Do people come back? (daily return rate) Do they watch more? (watch time per session) These are the real goals. You can only measure them by shipping and watching real users.
What you can train on (a fast stand-in). The real goals are slow to measure, so you train on something quicker: engagement per impression. An impression is one item shown to one user, one time. Did they click it? Did they finish the video? These signals show up in minutes, so a model can learn from them.
What you check before shipping (offline). Before anything goes live, you score it on held-out data. Two numbers matter here. Recall@K grades retrieval: of the items the user actually engaged with, how many made it into the top K candidates? AUC (or a ranking loss) grades the ranker: how often does it score a good item above a bad one?
Then say how these levels connect, because interviewers listen for it. Offline numbers only decide which models earn a live A/B test. Only the online number decides a launch. Offline is a filter, never the final word.
One more constraint carried over from the frame: about 200 ms for the whole page. That caps how much model work you can afford on the request path. The full cost story comes in step 7.
3. Map the Data: Impressions Are the Labels
You have three kinds of data: interaction logs, catalog content, and signup signals.
The logs are where your training labels come from. Log every impression along with the features you used to rank it. Log every click and watch with a timestamp. Join those together and the training examples build themselves. Shown and engaged is a positive example. Shown and skipped is a negative one.
This is the lucky part of the problem: these labels arrive within minutes, in huge volume. But they have two flaws, and it is worth naming them now.
- They only cover items the system chose to show. The model never learns anything about the items it never surfaced.
- They carry the bias of the slot they appeared in. An item shown first looks better than it really is.
Step 6 fixes both.
Catalog content and signup signals cover what the logs cannot: brand-new users and brand-new items. The cold-start deep dive in step 5 runs on these. Freshness comes in tiers. Refreshing item data nightly is fine. But the user's short-term state has to update during the session, not tomorrow.
4. Pick the Model: One Model Per Funnel Stage
There is no single "the model" here. Each stage of the funnel gets its own, and each one is the biggest model its volume can afford.
Candidate generation runs several cheap sources at the same time. Here is each one and why it earns a spot:
- Collaborative filtering with embeddings. The workhorse. A two-tower model learns a short list of numbers (a vector) for each user and each item. Users land close to the items they engage with, so "similar" becomes something you can measure as distance. How you serve this comes in step 5.
- Co-occurrence. "People who watched X also watched Y." These are item-to-item lists you compute ahead of time, looked up by the user's recent watches. Cheap, easy to explain, and surprisingly strong.
- Non-personalized sources. Trending now, new uploads, popular in the user's region. These carry brand-new users and keep the feed fresh.
The ranker. By now the list is small, so you can afford rich features: the user's full history, item statistics, user-item combinations, and context like device and hour. It predicts the engagement events you defined in step 2. Most of your modeling effort lives here.
If the interviewer asks for a baseline, stay honest: "most popular plus recent" is the simplest thing that works. Everything above is the upgrade path from there.
5. The Serving Path: A Funnel That Ends in Rules
Stage 1: candidate generation. 50 million down to about 500. The sources run at the same time, and you merge their results. The item vectors are computed ahead of time and loaded into an approximate nearest neighbor (ANN) index. In plain terms: instead of comparing the user against all 50 million items, the index uses a clever shortcut structure (HNSW is the usual one) to find the closest matches fast. The trade is that it is approximately right instead of exactly right, which is fine here. At request time, you grab the user's vector and pull the nearest few hundred items in a few milliseconds. That is how "50 million to 500" is even possible.
Stage 2: ranking. About 500 down to about 50. Here is a latency detail that lands well in interviews: scoring 500 items in one batched call takes 20 to 40 ms on a GPU, or on a well-tuned CPU fleet.
Stage 3: re-ranking. Business judgment on top. The score-ordered list is not what goes on screen yet. First you apply the rules: diversity (not 20 videos from one channel), freshness boosts, policy demotions, dropping items you just showed the user, and editorial pins. Keep this layer outside the model, as plain rules. The reason is practical, and worth saying out loud: product teams can turn these knobs without retraining anything.
Deep dive: cold start, both kinds
Cold start means the system has no history to work with. It comes in two flavors, and the interviewer will ask about both. Bring them up before they do.
- A new user. No history, so the personalized sources come up empty. Fall back to popularity and regional trends. Use whatever the signup gave you (language, declared interests, platform). Then treat that first session like a fast interview: weight their early clicks heavily and update their state during the session, not overnight.
- A new item. No engagement yet, so the collaborative sources cannot find it. Bridge the gap with content features (title, tags, audio and visual embeddings) so the item can enter the candidate pool by looking similar to items that are already established. Add a small exploration boost, since a slice of traffic buys honest labels. Skip this step and you get rich-get-richer: the popular videos keep winning and new ones never get a chance.
6. Keep It Working: The Loop and Its Traps
The rhythm of keeping this alive:
- Retrain the ranker every day.
- Rebuild item embeddings and the ANN index nightly, with quick incremental updates for new items.
- Update each user's short-term state during their session, through the streaming path.
Now the two traps. Here is the tricky part of the whole design, and interviewers know it, so raise them yourself.
- Position bias. Items shown first get clicked more, no matter how good they are. It is like judging books by which shelf the store happened to put them on. Train naively on those clicks and the model learns "whatever we ranked first must be best," which just freezes yesterday's ranking in place forever. The standard fixes: feed the display position in as a training feature and then zero it out at serving time, or weight each example by how unlikely a click was at that position (the technical name is inverse propensity weighting).
- The proxy trap. A proxy is a stand-in metric, one you optimize because the real goal is too slow to measure. Optimize for clicks and you get clickbait. Optimize for watch time and you get videos padded to run long. The grown-up answer is to weigh several objectives at once, and to admit that the real target (long-term retention) only shows up in A/B tests. This is a preview of feed ranking, where the objectives multiply.
7. Count the Costs: Precompute Heavy, Serve Cheap
The cost shape is simple: almost everything expensive happens offline, ahead of time. Nightly jobs embed and index millions of videos that nobody will even ask for tomorrow. That waste is on purpose. It is the price you pay to keep the request path cheap enough for 100 million users. The only real per-request cost is the ranker's one batched call. That is exactly why stage 2 scores 500 items and not 5,000.
Failures follow the same shape, and the good news is they fail soft:
- Nightly build breaks? Serve yesterday's index. Recommendations get a little stale, and nobody's homepage throws an error.
- Ranker times out? Fall back to candidate-generation order, or to trending.
So you alert on index age and fallback rate, not on user-visible errors, because there should not be any user-visible errors to alert on.
💡 If you are asked to estimate infrastructure: item embeddings are 50M x 128 floats x 4 bytes, which is about 25 GB. That fits in memory across a few ANN replicas. Saying that arithmetic out loud turns an abstract design into an engineered one.
8. In the Interview
The 30-second answer: "It is a three-stage funnel. First, candidate generation cuts 50 million videos down to about 500, using a few cheap sources in parallel: a two-tower embedding model served from an ANN index, item-to-item co-occurrence, and a trending-plus-new source for cold start and freshness. Second, a feature-rich ranker scores those 500 for predicted engagement in one batched call. Third, a rule layer adds diversity, freshness, and policy before anything hits the screen. I log every impression and its outcome, retrain the ranker daily, refresh embeddings nightly, and correct for position bias during training. Offline recall and AUC decide which models get an A/B test; long-term engagement decides what actually ships."
Likely follow-ups:
- "Why not one model end to end?" Money and physics. No model good enough to rank well is cheap enough to run 50 million times on every request. The funnel is the compromise. And each stage gets judged on its own job: retrieval on recall, ranking on precision at the top.
- "Recall looks fine, but users seem bored. Where do you look?" At the re-rank layer and the objective, not retrieval. Maybe diversity collapsed and every candidate comes from one taste cluster. Or your stand-in metric is over-serving short-term clicks. Break watch time down by content category over time and look for it narrowing. This is a feedback-loop symptom, not a model bug.
- "How do you do real-time personalization?" Session events stream into the user's short-term features within seconds. The user embedding gets a quick in-session update, or a fast recency-weighted stand-in. Full retraining stays nightly. Two tiers of freshness, and you pay for each.
- "Your nightly embedding build fails. What do users see?" Nothing, if the design is right. The ANN replicas keep serving yesterday's index. New items lean on content features and the trending source. An alert fires on index age. A second failure in a row starts to hurt new-item discovery, so the pager severity should climb with staleness.
- "Someone proposes a new candidate source. How do you decide it earns a slot?" Measure its marginal value, not its standalone recall. What share of engaged impressions came from candidates only this source found? Run it in shadow first, then a small A/B. A source that just re-finds what the two-tower already returns adds latency, not lift.
9. Common Mistakes
- Weak: starts by picking a model architecture. Strong: starts with 50 million items against a 200 ms budget, which forces the funnel before any model gets named.
- Weak: treats clicks as the plain truth. Strong: brings up position bias unprompted and offers a fix (position as a training feature, or inverse propensity weighting).
- Weak: buries diversity and policy inside the ranking model. Strong: keeps re-ranking as a separate rule layer, so product teams can turn knobs without retraining.
- Weak: waits to be asked about cold start. Strong: brings up both kinds, with different fixes for new users (popularity plus in-session learning) and new items (content features plus exploration).
10. Same Design, Other Questions
- "Design 'customers also bought' for an online store." What changes: co-occurrence becomes the main source, and purchases replace watch time as the label (rarer, but cleaner). The funnel, cold start, and bias story all carry over unchanged.
- "Design podcast discovery for an audio app." What changes: episodes are long, so "did they finish it" labels are slow and weak. Content embeddings from the audio and transcript now do more of the retrieval work, and the new-item path dominates because new episodes ship every week.
11. TL;DR
| Shape | Funnel: multi-source retrieval (~500) → feature-rich ranking (~50) → rule re-rank (screen). |
| Retrieval workhorse | Two-tower embeddings in an ANN index; plus co-occurrence and trending sources. |
| Cold start | New user: popularity + in-session learning. New item: content features + exploration. |
| Training traps | Position bias (feature or propensity fix); clickbait proxy trap (multi-objective). |
| Metrics | Offline recall/AUC gate experiments; online watch time and retention decide. |
| Cost shape | Precompute heavy, serve cheap: batch jobs fail soft to yesterday's index; ~25 GB of item embeddings fits in memory. |
On This Page
- Frame the Problem: The Scale Is the Design
- Define Success: Online Truth, Offline Proxies
- Map the Data: Impressions Are the Labels
- Pick the Model: One Model Per Funnel Stage
- The Serving Path: A Funnel That Ends in Rules
Deep dive: cold start, both kinds
- Keep It Working: The Loop and Its Traps
- Count the Costs: Precompute Heavy, Serve Cheap
- In the Interview
- Common Mistakes
- Same Design, Other Questions
- TL;DR