Recommendation System Components
Foundational building blocks reused across recommendation and ranking systems: candidate generation (two-tower models, collaborative filtering), scoring and ranking (learning-to-rank, cross-features), and re-ranking with position bias correction, calibration, and exploration-vs-exploitation. Grounded in Khang Pham's ML Primer and Recommendation System Components chapters.
▶📚 Prerequisites(3)
🎓 Learning objectives
- •Describe the three-stage recommendation pipeline (candidate generation → ranking → re-ranking) and what each stage optimizes for
- •Explain how a two-tower model works and why it enables fast candidate retrieval at scale
- •Distinguish collaborative filtering from content-based filtering and identify when each is appropriate
- •Describe the features used in a learning-to-rank model and how pointwise, pairwise, and listwise loss functions differ
- •Explain position bias and describe two methods for debiasing training data
- •Define calibration in the context of click-through rate (CTR) prediction and explain why miscalibrated scores cause poor decisions
- •Compare greedy exploitation vs. exploration strategies (epsilon-greedy, UCB, Thompson sampling) and their trade-offs
What is it?
A recommendation system decides which items from a large catalog to surface to a user. Almost every major product you use — Netflix, YouTube, Amazon, Spotify, TikTok, LinkedIn — is built on a stack of recommendation components.
Khang Pham (ML Primer, Recommendation System Components) describes the universal three-stage pipeline that underlies most large-scale systems:
- Candidate generation: narrow the catalog from millions of items to hundreds of plausible candidates using fast approximate retrieval
- Ranking: score each candidate on a precise, expensive feature set — typically a deep neural network — and select the top-K
- Re-ranking: apply final business rules, diversity constraints, freshness boosts, position bias corrections, and exploration strategies before presentation
Each stage makes a different accuracy-speed trade-off. Understanding the role of each component — and what goes wrong when it's missing or misconfigured — is foundational ML engineering knowledge that transfers across every recommendation domain.
Why it exists
You cannot score all N items for all M users at query time when N = 10 million and M = 100 million — that's 10^15 operations per second. The three-stage pipeline solves this with a funnel: cheap-but-approximate retrieval narrows 10M items to 500 candidates in milliseconds; expensive-but-precise ranking scores those 500 carefully; re-ranking polishes the final list for presentation.
Each stage also corrects for different failure modes:
Candidate generation — without it, you'd have to run a neural ranker on every item in the catalog. Two-tower retrieval makes this computationally feasible.
Ranking — without it, candidate generation's approximate signals (dot product of embedding vectors) miss the rich cross-feature interactions (user age × item genre, time-of-day × content type) that predict actual engagement.
Re-ranking — without it, the ranking model optimizes for predicted CTR on individual items, ignoring diversity (you'd show 10 Taylor Swift songs in a row), position bias (items shown at position 1 get clicked regardless of quality), and exploration (you'd never show new items, causing the filter bubble problem).
Problem it solves
- Catalog has 10M items — I need to retrieve relevant candidates in < 50ms for each user.
- New users have no history — cold start problem: no collaborative filtering signal available.
- My CTR model is trained on biased data — items shown at position 1 get clicked 3x more than position 5, regardless of quality. The model learns position, not quality.
- My ranking model predicts 8% CTR for an item, but actual CTR is 2%. Miscalibration causes the system to systematically overestimate engagement.
- My system keeps recommending the same popular items — new/niche content never gets exposure, creating a popularity feedback loop.
- I need to balance showing the best predicted items (exploitation) vs. trying items I haven't measured yet (exploration).
Intuition
Think of a recommendation system as a talent agency finding performers for a show.
Candidate generation = the agency's database lookup: 'Who are the 500 performers available tonight that roughly match what this venue needs?' — a fast, approximate search across millions of profiles.
Ranking = the booking agent carefully evaluates those 500: checks their specific skills, the venue's exact requirements, the audience demographic, and recent reviews. Narrows to the top 20.
Re-ranking = the show producer reviews the final list: 'We have 5 singers — show them in a good order, make sure there's variety, don't put two ballads in a row, and give one slot to a promising newcomer we haven't tried before.'
Each stage adds precision but costs more time. The agency can check 100,000 profiles in minutes (candidate generation), but can't interview 100,000 performers (ranking). The show producer can only review 20 final candidates (re-ranking) but does so with full context the database and agent couldn't access.
Analogy
The three-stage recommendation pipeline is like a court case with three phases.
Candidate generation = discovery phase: 'Which of the 10,000 potential witnesses are even plausibly relevant to this case?' — cheap, broad, approximate filtering. You don't depose everyone; you identify the 100 who might know something.
Ranking = deposition phase: 'For each of these 100 witnesses, how strong is their testimony? What do they know, what are their biases, how credible are they?' — expensive, detailed evaluation of a small set.
Re-ranking = trial preparation: 'Of our top 20 witnesses, in what order do we call them? Make sure we have variety of perspectives. Don't start with the weakest witness. Save the star witness for closing. And let's try one witness we've never used before to see how they perform.' — strategic arrangement of the final list.
Technical explanation
CANDIDATE GENERATION (Khang Pham, ML Primer):
Two-Tower Model: Two separate neural networks (towers) produce embeddings for users and items independently. User tower input: user_id embedding, demographics, behavioral features (watch history, clicks) Item tower input: item_id embedding, content features (category, tags, description embedding) Training: contrastive learning — positive pairs (user, item they engaged with) should have high dot product; negative pairs (user, random item) should have low dot product. Loss: in-batch negatives (treat other users' items in the batch as negatives for each user) — efficient. Online serving: pre-compute all item embeddings → store in FAISS ANN index. At query time: compute user embedding → query FAISS for top-K nearest item embeddings → return candidates. Speed: ~10ms for top-100 candidates from 10M items using FAISS HNSW.
Collaborative Filtering: Matrix factorization: decompose user-item interaction matrix R ≈ U × Vᵀ. User latent vector U_i and item latent vector V_j — high dot product means user i will likely engage with item j. Strengths: captures taste communities (users who liked A and B will also like C) Weakness: cold start (new users/items with no interactions can't be embedded)
Content-Based Filtering: Represent items by their features (genre, description embedding, tags). Represent user preference by aggregating features of items they engaged with. Recommend items whose feature vector is closest to the user's preference vector. Strengths: no cold start for items (works immediately for new items with features) Weakness: over-specialization (user who liked mystery novels only gets mystery novels)
RANKING (SCORING):
Feature Engineering: Item features: category, tags, age, content type, historical engagement rates User features: demographics, device, time-of-day, session length, recent clicks Cross features (most powerful): user_age × item_category, user_location × item_language Cross features capture interaction effects: a 25-year-old in the US responds differently to K-pop than a 50-year-old in Japan — user age alone or item genre alone doesn't capture this.
Learning-to-Rank: Pointwise: treat ranking as regression — predict engagement probability per item independently. Pairwise (RankNet, LambdaRank): for each (item_i, item_j) pair, predict which item the user prefers — optimizes relative order. Listwise (LambdaMART, SoftMax loss): optimize metrics like NDCG over the full list — most aligned with actual ranking quality but harder to train. In practice: most production systems use pointwise losses (CTR regression) for training simplicity, with ranking determined by score ordering.
Deep Ranking Models: Wide & Deep (Google, 2016): wide = memorization (linear model on sparse cross-features), deep = generalization (MLP on dense embeddings). Combines both. DCN (Deep & Cross Network): automatically generates polynomial feature interactions without hand-engineering. DIN (Deep Interest Network, Alibaba): attention over user's behavioral history to weight relevant past interactions for the current item.
RE-RANKING:
Position Bias: Items shown at position 1 are clicked more than items at position 5, regardless of quality. If you train on raw click data: the model learns 'position 1 gets clicks' not 'quality gets clicks'. Debiasing approaches: a. Position as feature at training time, set to a constant (e.g., position=1) at inference b. Inverse Propensity Scoring (IPS): weight each training example by 1/P(impression at position k) c. Randomization experiments: occasionally shuffle positions randomly to collect unbiased click signals
Calibration: A CTR model might predict 8% for an item but actual CTR is 2%. Miscalibration causes: incorrect item ordering, wrong bid prices in ads auctions, bad business decisions. Calibration check: plot predicted CTR vs. actual CTR across deciles — a perfectly calibrated model lies on the diagonal. Fix: Platt scaling (logistic regression on model outputs), isotonic regression, or temperature scaling.
Exploration vs. Exploitation: Exploitation: always show the item with the highest predicted score. Problem: you never discover that a new item might be better. Popular items stay popular; new items never get a chance.
Exploration strategies: ε-greedy: with probability ε, show a random item; with probability 1-ε, show the best predicted item. UCB (Upper Confidence Bound): show item i that maximizes Q_i + √(2 ln t / n_i) — favors items with high uncertainty. Thompson Sampling: model each item's reward as a distribution; sample from each distribution; show the item with the highest sample. Softmax exploration: sample from a softmax distribution over predicted scores, so high-scoring items are more likely but not guaranteed.
Diversity (Maximal Marginal Relevance, MMR): Select items that are relevant AND dissimilar to already-selected items. MMR: score_i = λ × relevance_i - (1 - λ) × max_j similarity(item_i, item_j) Prevents showing 10 near-identical items even if they all score highly.
Architecture
Production Recommendation System Stack:
┌─────────────────────────────────────────────────────────────┐ │ OFFLINE TRAINING PIPELINE │ │ │ │ User interaction logs ──► Feature engineering │ │ (clicks, purchases, (user features, item features, │ │ ratings, dwell time) cross features, debiasing) │ │ │ │ │ │ Two-Tower Training Ranker Training │ │ (contrastive loss) (CTR regression) │ │ │ │ │ │ User/Item embeddings Ranking model │ │ │ │ │ │ FAISS ANN Index Model registry │ │ (pre-built from item embs) (versioned artifacts) │ └────────────────────────┬────────────────────────────────────┘ │ (batch update: daily or hourly) ┌────────────────────────▼────────────────────────────────────┐ │ ONLINE SERVING (per request, < 100ms) │ │ │ │ User Request │ │ │ │ │ [Feature Store Lookup] ← user features, real-time context │ │ │ │ │ [Two-Tower: compute user_emb → FAISS query] │ │ │ → 200-500 candidates │ │ │ │ │ [Ranker: compute rich features for each candidate] │ │ │ → score each, sort descending │ │ │ → top 50 candidates │ │ │ │ │ [Re-ranker: diversity, freshness, bias correction, │ │ exploration, business rules] │ │ │ → final 10-20 items │ │ ▼ │ │ Response to UI │ └─────────────────────────────────────────────────────────────┘ │ [Logging + Feedback] ← impressions, clicks, engagement │ [Offline Training Pipeline] ← closes the loop
Workflow
Building a recommendation system from scratch:
Phase 1 — Simple baseline (week 1): a. Popularity-based: return the top-N most-clicked items globally b. No personalization — fast to build, establishes baseline metrics c. Measure: CTR, dwell time, conversion rate
Phase 2 — Add candidate generation (week 2-4): a. Collect user-item interaction data b. Train a two-tower model (or matrix factorization if data is limited) c. Build FAISS ANN index over item embeddings d. A/B test against popularity baseline
Phase 3 — Add ranking (week 4-8): a. Engineer features: user profile + item features + cross features b. Train CTR regression model (gradient boosting or Wide & Deep) c. Score the 200-500 candidates from the two-tower retrieval d. A/B test ranker vs. two-tower-only ordering
Phase 4 — Add re-ranking and debiasing (week 8-12): a. Analyze position bias: compare CTR at position 1 vs. position 10 b. Add position as training feature, use IPS weighting c. Add diversity (MMR) to prevent repetitive lists d. Add exploration (ε-greedy) for new content e. Measure calibration: plot predicted CTR vs. actual CTR
Phase 5 — Scale and iterate (ongoing): a. Move to real-time feature serving (feature store) b. Add contextual features (time, device, session) c. Close the training loop: use logged impressions + clicks as training data d. Monitor for data drift, position bias drift, popularity feedback loops
Example
# Simplified two-tower candidate generation import numpy as np from sklearn.metrics.pairwise import cosine_similarity # --- Two-Tower: simplified representation --- # In production: PyTorch/TF model with user/item ID embeddings + feature MLPs # Here: stub with random unit vectors to demonstrate the retrieval pattern N_ITEMS = 10000 EMBED_DIM = 128 np.random.seed(42) # Pre-compute item embeddings (done offline, stored in FAISS) item_embeddings = np.random.randn(N_ITEMS, EMBED_DIM) item_embeddings /= np.linalg.norm(item_embeddings, axis=1, keepdims=True) def get_user_embedding(user_id: int) -> np.ndarray: # In production: forward pass through user tower np.random.seed(user_id) # deterministic per user v = np.random.randn(EMBED_DIM) return v / np.linalg.norm(v) def two_tower_retrieve(user_id: int, top_k: int = 200) -> list[int]: user_emb = get_user_embedding(user_id) # shape: (128,) # In production: FAISS ANN query — O(log N) scores = item_embeddings @ user_emb # dot product: shape (N_ITEMS,) top_indices = np.argsort(scores)[::-1][:top_k] return top_indices.tolist() # --- Ranker: score candidates with richer features --- from sklearn.linear_model import LogisticRegression def make_ranking_features(user_id: int, item_ids: list[int]) -> np.ndarray: # In production: fetch from feature store, compute cross features # Here: stub features [item_popularity, user_item_affinity] np.random.seed(user_id * 1000) features = [] for iid in item_ids: item_pop = (iid % 100) / 100.0 # simulated popularity 0-1 affinity = np.random.rand() # simulated user-item affinity features.append([item_pop, affinity]) return np.array(features) def rank_candidates(user_id: int, candidate_ids: list[int], ranker: LogisticRegression, top_k: int = 50) -> list[int]: features = make_ranking_features(user_id, candidate_ids) scores = ranker.predict_proba(features)[:, 1] # P(click) ranked_indices = np.argsort(scores)[::-1][:top_k] return [candidate_ids[i] for i in ranked_indices] # --- Re-ranker: MMR diversity + epsilon-greedy exploration --- def mmr_rerank(ranked_ids: list[int], lmbda: float = 0.7, top_k: int = 20) -> list[int]: """Maximal Marginal Relevance: balance relevance vs. diversity.""" selected = [] remaining = list(ranked_ids) # relevance score: rank order (first = most relevant) relevance = {iid: 1.0 / (rank + 1) for rank, iid in enumerate(ranked_ids)} while remaining and len(selected) < top_k: if not selected: best = remaining[0] # pick most relevant first else: # pick item maximizing: lmbda * relevance - (1-lmbda) * similarity_to_selected def mmr_score(iid): # sim to selected: use item_embeddings for real similarity max_sim = max( float(item_embeddings[iid] @ item_embeddings[s]) for s in selected ) return lmbda * relevance[iid] - (1 - lmbda) * max_sim best = max(remaining, key=mmr_score) selected.append(best) remaining.remove(best) return selected def epsilon_greedy_explore(ranked_ids: list[int], all_item_ids: list[int], epsilon: float = 0.1) -> list[int]: """With probability epsilon, replace one item with a random unexplored item.""" result = list(ranked_ids) if np.random.rand() < epsilon: explored_items = set(ranked_ids) unexplored = [i for i in all_item_ids if i not in explored_items] if unexplored: replace_idx = np.random.randint(len(result)) result[replace_idx] = np.random.choice(unexplored) return result # --- Full pipeline --- def recommend(user_id: int, ranker: LogisticRegression) -> list[int]: # Stage 1: candidate generation candidates = two_tower_retrieve(user_id, top_k=200) # Stage 2: ranking ranked = rank_candidates(user_id, candidates, ranker, top_k=50) # Stage 3: re-ranking (diversity + exploration) diverse = mmr_rerank(ranked, lmbda=0.7, top_k=25) final = epsilon_greedy_explore(diverse, list(range(N_ITEMS)), epsilon=0.1) return final[:20] # return top 20 # Demo (ranker stub: random training data) X_train = np.random.rand(500, 2) y_train = (X_train[:, 1] > 0.5).astype(int) # high affinity → clicked ranker = LogisticRegression().fit(X_train, y_train) recs = recommend(user_id=42, ranker=ranker) print(f'Top 20 recommendations for user 42: {recs[:5]}...')
Real-world usage
-
YouTube (Google, 2016 paper): two-tower candidate generation over 1M+ videos at O(1) cost per video (pre-computed embeddings); deep neural ranking with hundreds of user features; the ranking model trains on watch time, not clicks — clicks are a noisy proxy for engagement but watch time is what the business optimizes.
-
TikTok: famously lightweight candidate generation (no social graph — pure content-based) combined with very fast feedback loop. A new video gets served to 100 users; if engagement is high, the system rapidly scales its distribution. The exploration mechanism is central to TikTok's ability to surface content from new creators.
-
Netflix prize (2006-2009): demonstrated that matrix factorization (collaborative filtering via SVD) dramatically outperforms hand-crafted content-based features for movie ranking. Kicked off the modern recommendation system era.
-
Amazon product recommendations: uses cross-feature models that capture 'users who bought X also bought Y' — collaborative filtering at item co-occurrence level, not at user-level. Avoids cold-start problem for items since co-occurrence can be computed from aggregate purchase logs without individual user profiles.
-
LinkedIn job recommendations: position bias correction is critical — jobs shown at the top of the feed get applications regardless of fit. LinkedIn uses randomization experiments to collect unbiased click data and IPS weighting during training.
Trade-offs
Candidate generation recall vs. precision: the retrieval stage should optimize for recall (don't miss good items). The ranking stage optimizes for precision (correctly rank what's retrieved). A retrieval stage with 90% recall means 10% of the best items are never shown to the user — no matter how good the ranker is. Test retrieval recall independently: sample known relevant items and check how often they appear in the candidate set.
Exploration vs. exploitation: more exploration (higher epsilon) helps new content get exposure and prevents filter bubbles, but reduces short-term CTR because you're showing unproven items. Less exploration (lower epsilon) maximizes short-term metrics but creates long-term content diversity problems. Most systems set epsilon to 10-20% and monitor new-content exposure rate as a separate KPI.
Collaborative filtering vs. content-based: collaborative filtering produces excellent recommendations for active users but fails for cold-start users with no history. Content-based filtering works immediately for new users (use their stated preferences or demographics to seed the feature vector) but is prone to over-specialization. Most production systems blend both: collaborative filtering for engaged users, content-based for cold-start.
Real-time features vs. batch features: real-time features (current session behavior, most recent click) improve ranking quality significantly but require a feature store with low-latency serving (< 10ms). Batch features (user's all-time preferences, historical averages) are cheaper to serve. Start with batch features, add real-time features incrementally as infrastructure matures.
Visual explanation
Three-Stage Recommendation Funnel:
ALL ITEMS 10,000,000 items │ ▼ ┌─────────────────────────────────────────────────────────┐ │ CANDIDATE GENERATION │ │ │ │ Techniques: Speed vs. Accuracy: │ │ • Two-tower retrieval Very fast (~10ms) │ │ • Collaborative filtering Approximate │ │ • Content-based filtering High recall needed │ │ • Popularity-based (don't miss good items)│ │ │ │ Goal: HIGH RECALL (don't miss the good items) │ └─────────────────────────────────────────────────────────┘ │ ▼ 200-1000 candidates ┌─────────────────────────────────────────────────────────┐ │ RANKING (SCORING) │ │ │ │ Features: Speed vs. Accuracy: │ │ • User × item cross-features Slower (~50ms) │ │ • Deep neural network High precision needed │ │ • Contextual signals Rich feature space │ │ (time, device, session) │ │ │ │ Goal: HIGH PRECISION (rank the truly best items first) │ └─────────────────────────────────────────────────────────┘ │ ▼ Top 50-100 ranked items ┌─────────────────────────────────────────────────────────┐ │ RE-RANKING │ │ │ │ Adjustments: Goal: │ │ • Diversity (MMR, DPP) Business-aligned list │ │ • Position bias correction Fair ordering │ │ • Calibration Accurate probability │ │ • Freshness boost New content exposure │ │ • Exploration (epsilon, UCB) Avoid filter bubble │ │ • Business rules (sponsored) Revenue/policy goals │ │ │ └─────────────────────────────────────────────────────────┘ │ ▼ Final list shown to user (10-20 items) PRESENTED RECOMMENDATIONS
Two-Tower Model Architecture:
USER TOWER ITEM TOWER
│ │
[User features] [Item features] user_id emb item_id emb age, location category, tags watch_history_emb avg_rating │ │ [Dense Layers] [Dense Layers] │ │ user_emb (128d) item_emb (128d) │ │ └────────────────────────┘ │ dot_product(user_emb, item_emb) │ → relevance score
OFFLINE: build FAISS/ANN index over all item_embs ONLINE: compute user_emb → ANN query → top-K items in ~10ms
Advantages
- —
Three-stage funnel makes recommendation at internet scale computationally feasible — approximate retrieval over millions of items in milliseconds
- —
Two-tower models generalize to new user-item pairs at inference time without retraining
- —
Re-ranking allows business logic (diversity, exploration, sponsored content) to be applied at the last moment without retraining the expensive neural ranker
- —
Exploration strategies (epsilon-greedy, UCB) prevent the popularity feedback loop — new and niche content gets a fair chance to prove itself
- —
Calibration ensures predicted CTR scores are meaningful for decision-making (bid optimization, ranking fusion across systems)
Disadvantages
- —
Cold start: collaborative filtering and two-tower models struggle with new users or new items with no interaction history
- —
Position bias corrupts training data: if you train on biased impressions without debiasing, the ranker learns 'position = quality' not 'relevance = quality'
- —
Popularity feedback loops: if the system only exploits, popular items stay popular and new content never gets exposure — the catalog becomes effectively smaller over time
- —
Candidate generation recall ceiling: items not retrieved in Stage 1 can never be recommended — a bug or gap in the retrieval index silently removes entire content categories
- —
Cross-feature explosion: the number of possible user × item cross-features grows quadratically — automatic feature interaction models (DCN, Transformer) are needed at scale
Common mistakes
- —
Training the ranker on raw click data without debiasing for position. If you log impressions and clicks without accounting for the fact that position 1 gets 3x more clicks, your ranker learns position preference not content quality. At minimum: add position as a feature during training and hold it constant (position=1) at inference so the model learns quality-conditional CTR.
- —
Ignoring retrieval recall. Most teams obsess over ranking quality (NDCG, MAP) on the items retrieved. But if the retrieval stage has 70% recall, 30% of the best content never enters the pipeline. Measure retrieval recall independently: sample ground-truth relevant items and check what fraction appears in the candidate set. A recall gap is often more impactful than ranking model quality.
- —
Not measuring calibration. A ranker that predicts 8% CTR for items that achieve 2% actual CTR is badly miscalibrated. This matters for: correct relative ranking (if all items are miscalibrated equally, ranking is unaffected; if calibration varies by category, ranking degrades), bid optimization (ads systems), and fusion (combining scores from multiple models). Plot predicted vs. actual CTR across deciles before every major model change.
- —
Over-exploiting from day one. Building a system without any exploration mechanism creates a popularity feedback loop: the most popular items at launch dominate training data, the model scores them highest, they get shown most, they accumulate more clicks, they score even higher. Add epsilon-greedy or UCB from the start, even if epsilon is small (10%). Monitor new-content exposure rate weekly.
- —
Skipping diversity, then wondering why engagement drops. A ranking model that optimizes CTR per item will happily show 10 near-identical items if they all score highly (10 action movies in a row). MMR diversity re-ranking adds minimal engineering cost but significantly improves session engagement — users don't just click the first item; they engage with a list.
🎤 Interview questions
Describe the three-stage recommendation pipeline. What does each stage optimize for and why can't you just run the ranker on all items directly?
How does a two-tower model work? Why are the two towers trained together but served independently?
What is position bias and how do you correct for it in training data?
Explain the exploration-exploitation trade-off in recommendations. What happens to a system that only exploits?
📂 Subtopics
Candidate Generation: Two-Tower Models and Collaborative Filtering
How to narrow millions of items to hundreds of candidates in milliseconds: two-tower retrieval, matrix factorization, and content-based approaches.
~45 min
Ranking: Feature Engineering and Learning-to-Rank
Scoring the candidate set with rich features and deep models: cross-feature engineering, CTR regression, and learning-to-rank losses.
~40 min
Re-ranking, Position Bias, Calibration, and Exploration
The final list adjustments that make rankings fair, calibrated, diverse, and safe for new content: position bias correction, calibration, MMR diversity, and exploration-vs-exploitation strategies.
~45 min