TL;DR — A social feed decides, in ~200 ms, which of thousands of eligible posts to show you and in what order. The standard architecture is three stages: candidate generation (pull a few thousand eligible posts from billions), ranking (a multi-task deep model predicts P(like), P(comment), P(share), P(dwell)… for every candidate), and re-ranking (combine those probabilities into one value score, then apply diversity, freshness, and integrity rules). The heart of the interview is the value model:
value = Σ wₖ · pₖ— a weighted sum of engagement predictions where the weightswₖencode what the business actually wants. You must also nail calibration (predicted probabilities must be real probabilities) and real-time serving.
1. Simple explanation
Your feed is not chronological. Every time you open the app, the system asks: "Of everything this user could see right now, what will they most want to engage with — without wrecking their long-term trust?"
Analogy — a newspaper editor with a deadline. Every morning an editor (the feed) has thousands of stories (candidate posts) and one front page (your screen). A good editor doesn't just pick the most clickable story — that leads to clickbait. They weigh many signals: will you read it (dwell), share it (reach), comment (community), or hide it (a negative signal)? They multiply each by how much that action matters (the value weights), then arrange the page so it's not ten copies of the same story (diversity), the news is fresh, and nothing is harmful (integrity). The feed does exactly this — for 3 billion editors' front pages, personalized, every few seconds.
2. Diagram
SOCIAL FEED RANKING (request-time, ~200ms budget)
user opens app
│
▼
┌─────────────────────┐ billions of posts → a few thousand
│ CANDIDATE GENERATION│ • from friends/follows (in-network)
│ (retrieval) │ • out-of-network via embeddings + ANN
└─────────┬───────────┘ • recent, unseen, eligible
│ ~1000–5000 candidates
▼
┌─────────────────────┐ ONE multi-task model scores EACH candidate:
│ RANKING (heavy) │ p_like, p_comment, p_share, p_dwell,
│ multi-task DNN │ p_hide (negative), p_report (negative) …
└─────────┬───────────┘ → a vector of calibrated probabilities per post
│
▼
┌─────────────────────┐ VALUE MODEL: V = Σ wₖ·pₖ (weighted sum)
│ RE-RANKING (light) │ then: diversity (no author/topic floods)
│ value + rules │ freshness boost · integrity demotion
└─────────┬───────────┘ ads/notifications interleave
│
▼
final ordered feed ──► log impressions + engagements
│
▼ (hours→days later)
labels → retrain the ranker (feedback loop)
3. How it works (the system-design flow)
3.1 Requirements
Functional: given a user + context, return an ordered list of posts that maximizes long-term engagement while respecting integrity/policy.
Non-functional (the numbers interviewers want):
| Constraint | Typical target |
|---|---|
| Latency (end-to-end feed) | p99 ≤ ~200–500 ms |
| Ranking model budget | ~10–50 ms for the whole candidate set |
| Scale | ~10⁹ users, ~10⁶ QPS peak, billions of eligible items |
| Freshness | new posts rankable within seconds/minutes |
| Objective | not raw CTR — long-term retention & healthy engagement |
Key framing point: proxy metrics (CTR) are optimized, but the true goal is retention/session quality. Optimizing pure clicks yields clickbait — this tension is the recurring theme.
3.2 Data & features
Three families — memorize this taxonomy, interviewers grade on it:
USER features : long-term interests (topic affinity embeddings), demographics,
historical engagement rates, follows/friends, device/locale
ITEM features : author, media type (text/photo/video/link), topic/embedding,
age (freshness), historical engagement (pop / CTR priors), language
CONTEXT features : time-of-day, day-of-week, device, network speed, session position,
what they JUST engaged with (session sequence)
CROSS features : user×author affinity, user×topic affinity, past interactions
with this author, embedding dot-products
Served from a feature store with two tiers: offline (batch-computed embeddings, historical aggregates — updated hourly/daily) and online/real-time (streaming counters like "posts liked in last 10 min," session sequence — updated in ms via Flink/Kafka). Real-time features are what make the feed feel responsive.
3.3 Candidate generation (retrieval)
Cut billions → a few thousand, cheaply. Sources are blended:
- In-network: posts from friends/pages you follow (a bounded set) — pulled directly.
- Out-of-network: the hard part. Represent user and posts as embeddings in the same space; do approximate nearest neighbor (ANN) search (HNSW / IVF-PQ, e.g. FAISS/ScaNN) to grab semantically-relevant posts you don't follow. This is the "two-tower" retrieval pattern.
- Heuristic sources: trending, freshly-posted, "because you followed X."
Recall matters here, not precision — you just need the good stuff to survive into the ranking stage.
3.4 Ranking (the multi-task model)
One deep model scores each candidate. Instead of predicting a single score, it predicts many engagement heads at once (multi-task learning), because a like, a comment, and a share are different signals with different value — and sharing a bottom network is more data-efficient than training separate models.
shared bottom (embeddings + dense layers)
/ | | | \
p_like p_comment p_share p_dwell p_hide (neg)
Modern versions use MMoE (Multi-gate Mixture-of-Experts) so tasks that conflict (e.g. dwell vs. share) don't fight over one shared representation — each task gets its own gated mix of shared "experts." Architectures range from GBDTs → Wide&Deep → DLRM/DCN → transformer sequence models over your recent actions.
3.5 Re-ranking & the value model ★
The ranking model gives you a vector of probabilities per post. The value model collapses it into one number to sort by:
V(post) = w_like·p_like + w_comment·p_comment + w_share·p_share
+ w_dwell·p_dwell − w_hide·p_hide − w_report·p_report
The weights encode product strategy: a comment or share may be worth 10–30× a passive like (it signals deeper investment and creates reach); a hide or report is a negative weight (predicted probability of a bad reaction subtracts value). Tuning these weights is how product teams steer the feed — and why calibration matters (below): the weighted sum is only meaningful if each pₖ is a true probability on the same scale.
Then apply re-ranking passes on the top-scored set:
- Diversity: don't show 8 posts from one author or one topic. Techniques: MMR (Maximal Marginal Relevance), determinantal point processes (DPP), or simple per-author/per-topic caps.
- Freshness: boost recent content so the feed isn't stale; decay old items.
- Integrity/safety: demote or remove borderline/policy-violating content (a separate classifier stack); this is a hard constraint, not a soft weight.
- Interleaving: slot in ads and notifications at the right density.
3.6 Serving
Request-time flow must fit the latency budget: fetch user features → candidate gen (ANN + in-network) → batch-score candidates on GPU/optimized CPU → value-score + re-rank → return. Post embeddings are precomputed; only the light user tower + ranking head run at request time. Cache aggressively (feature cache, embedding cache). Log every impression and engagement — that log is your next training set.
3.7 Evaluation
- Offline (ranking quality): AUC / ROC-AUC per task, NDCG@k, calibration error (ECE), per-task log-loss. Offline gains must translate online, or they're noise.
- Online (the truth): A/B test with guardrails — CTR, dwell time, session length, DAU/retention, and negative guardrails (hide rate, report rate, complaint rate). Ship only on statistically-significant lifts that don't regress integrity metrics.
3.8 Scale
Sharded candidate stores, GPU inference servers with dynamic batching, embedding caches, streaming feature pipelines, and continuous/near-real-time retraining. Feeds are frequently retrained daily or faster because engagement distributions drift fast (news cycles, trends).
4. The math
4.1 Multi-task engagement prediction
Each head is a binary classifier trained with log-loss (binary cross-entropy):
L_k = − (1/N) Σ_j [ y_kj·log(p_kj) + (1−y_kj)·log(1−p_kj) ]
y_kj = did user j do action k on this post? (0/1) p_kj = predicted prob
Total loss is a weighted sum over the T tasks (these training weights are separate from the value weights):
L = Σ_{k=1..T} α_k · L_k
4.2 The value score (what you sort by)
V(u, post) = Σ_{k=1..T} w_k · p_k(u, post)
w_k = business value of action k (shares/comments high; hide/report NEGATIVE)
p_k = CALIBRATED predicted probability of action k
Sort candidates by V descending, then apply diversity/freshness/integrity re-ranking.
4.3 Calibration (why it's non-negotiable here) ★
A model can rank well but output miscalibrated probabilities (says 0.9, only right 60% of the time). Because the value model adds probabilities across tasks and multiplies by weights, miscalibration silently corrupts the weighted sum (and any downstream auction, e.g. ads). Calibration error is often measured with Expected Calibration Error:
ECE = Σ_b (|B_b|/N) · | acc(B_b) − conf(B_b) |
bucket predictions into bins b; compare avg predicted prob (conf)
to actual observed frequency (acc) in each bin. ECE=0 → perfectly calibrated.
Fixes applied after training on a held-out set:
- Platt scaling — fit a logistic on the model's scores (best when the distortion is sigmoid-shaped, and when calibration data is scarce).
- Isotonic regression — a monotonic, non-parametric map via Pool-Adjacent-Violators; more flexible, but needs ≳1000 points and can overfit on little data.
4.4 NDCG (offline ranking metric)
DCG@k = Σ_{i=1..k} (2^rel_i − 1) / log2(i + 1) NDCG@k = DCG@k / IDCG@k
rel_i = relevance/graded engagement of item at rank i; IDCG = DCG of the ideal order.
→ rewards putting high-value posts near the TOP (position-discounted). Range 0..1.
5. Real code
A faithful minimal multi-task ranker + value model + calibration, in the shape production uses.
import torch, torch.nn as nn
class MultiTaskRanker(nn.Module):
"""Shared bottom -> several engagement heads (like/comment/share/dwell/hide)."""
def __init__(self, in_dim, tasks=("like", "comment", "share", "dwell", "hide")):
super().__init__()
self.shared = nn.Sequential(
nn.Linear(in_dim, 256), nn.ReLU(),
nn.Linear(256, 128), nn.ReLU(),
)
# one small head per objective; sigmoid -> probability
self.heads = nn.ModuleDict({t: nn.Linear(128, 1) for t in tasks})
def forward(self, x):
z = self.shared(x)
return {t: torch.sigmoid(head(z)).squeeze(-1) for t, head in self.heads.items()}
# ---- value model: weighted sum of CALIBRATED probs (shares>comments>likes; hide is negative)
VALUE_WEIGHTS = {"like": 1.0, "comment": 8.0, "share": 15.0, "dwell": 3.0, "hide": -20.0}
def value_score(probs: dict) -> torch.Tensor:
return sum(VALUE_WEIGHTS[t] * probs[t] for t in VALUE_WEIGHTS)
# ---- request-time ranking with a simple per-author diversity cap
def rank_feed(model, candidate_feats, authors, cap_per_author=3, top_n=20):
with torch.no_grad():
probs = model(candidate_feats) # dict of [num_candidates] prob tensors
scores = value_score(probs) # [num_candidates]
order = torch.argsort(scores, descending=True).tolist()
feed, seen = [], {}
for idx in order: # greedy re-rank with author diversity
a = authors[idx]
if seen.get(a, 0) >= cap_per_author: # skip if this author already flooded
continue
feed.append(idx); seen[a] = seen.get(a, 0) + 1
if len(feed) >= top_n:
break
return feed
# ---- calibrate a head's probabilities AFTER training (isotonic on a held-out set)
from sklearn.isotonic import IsotonicRegression
def calibrate(raw_probs, labels):
iso = IsotonicRegression(out_of_bounds="clip")
iso.fit(raw_probs, labels) # monotonic map raw_prob -> true freq
return iso # apply iso.predict(p) at serving time
6. Real-world example
- Meta (Facebook / Instagram) Feed — the canonical multi-stage design: candidate gen (in-network + out-of-network embedding retrieval) → a multi-task DNN predicting many engagement events → a value model that weights them, with heavy integrity demotion and diversity passes. Meta publicly frames ranking as predicting meaningful interactions, not raw clicks, and uses MMoE-style multi-task models.
- Twitter/X open-sourced its algorithm: candidate sourcing (in-network via real-graph, out-of-network via embeddings/GraphJet) → a ~48M-parameter multi-task neural net predicting engagement probabilities → a weighted score → heuristics/filters (author diversity, "out-of-network" caps, visibility filtering).
- LinkedIn Feed — explicitly balances a value model across "creator" side (does this help the poster?) and "member" side (does this help the reader?), with strong diversity/freshness because a professional feed flooded by one viral post is bad UX.
- Natural ties: the candidate-generation → ranking → re-ranking two-stage pattern is the same backbone described in Recommendation Systems (ANN retrieval + selector) and Search Systems (retrieval → LTR). The value model's weighted multi-objective trick is the engagement cousin of a group recommender's
avg − λ·Varfairness selector — both collapse several sub-scores into one tunable objective.
7. Interview questions companies actually ask
Q [easy] "Why isn't the feed just reverse-chronological?"
A Chronological ignores relevance: a user following 1000 accounts drowns. Ranking surfaces
the few high-value posts first, lifting engagement and retention. Cost: less predictable,
needs integrity guardrails, and can create filter bubbles.
Q [easy] "What features would you use to rank a post?"
A Three families: USER (interest embeddings, historical engagement), ITEM (author, media
type, age, topic embedding, popularity priors), CONTEXT (time, device, session sequence),
plus CROSS features (user×author affinity, user×topic). Served from a feature store.
Q [medium] "Why multi-task learning instead of one 'engagement' score?"
A Different actions carry different value and different label densities. Multi-task shares a
bottom (data-efficient, regularizing) while predicting p_like/p_comment/p_share/p_hide
separately, so a value model can weight them — and negatives (hide) get their own head.
Q [medium] "What is the 'value model' and how do you set the weights?"
A V = Σ wₖ·pₖ over calibrated engagement probs; wₖ = business value (shares/comments ≫ likes,
hide/report negative). Weights are tuned via online A/B experiments against long-term
guardrails (retention, complaint rate), NOT hand-picked to max short-term CTR.
Q [medium] "Why does calibration matter for a feed ranker?"
A The value model ADDS probabilities across tasks; if p's aren't true probabilities, the
weighted sum is garbage (and any ad auction breaks). Fix with Platt scaling or isotonic
regression on held-out data; monitor Expected Calibration Error.
Q [hard] "How do you keep the feed diverse and fresh?"
A Re-ranking pass after scoring: per-author/per-topic caps, MMR or DPP for diversity;
freshness boosts/decays on item age. These are constraints layered on top of the value
score, tuned so diversity doesn't tank engagement.
Q [hard] "Optimizing CTR gave more clicks but worse retention. What happened, what do you do?"
A Classic proxy-metric trap: CTR rewards clickbait/outrage that erodes trust. Add negative
objectives (hide/report/'see less'), reweight the value model toward dwell/meaningful
interactions, add integrity demotion, and evaluate on retention guardrails in the A/B test.
Q [hard] "How do you serve this within a ~200ms budget at 10^6 QPS?"
A Precompute item embeddings; run only the light user tower + ranking head at request time;
ANN for out-of-network candidate gen; GPU dynamic batching; feature+embedding caches;
bound candidate set (~few thousand). Re-ranking is O(k) on the top set only.
Q [hard] "How do you handle position bias / feedback loops in training labels?"
A Users click what's shown high, so logs are biased toward the ranker's own choices. Correct
with inverse-propensity weighting, add exploration (randomize a slot / bandits), and log
the presented position as a feature to de-bias, so the model learns relevance not position.
Sources: ByteByteGo — Personalized News Feed · Meta ML System Design Guide (2026) · Designing a Newsfeed Ranking System · Meta System Design Interview — Exponent · Predicting Good Probabilities (calibration, Niculescu-Mizil & Caruana) · Calibration: Platt vs Isotonic — KDnuggets
8. When to use / tradeoffs
Chronological feed → tiny/new networks, trust/transparency-critical (know exactly what you get).
Single-objective ML → simple, but invites clickbait; only ok when one action truly = value.
Multi-task + value → the production default at scale; steerable, but complex + needs calibration.
Heavy re-ranking → when diversity/freshness/integrity matter (they always do at scale);
adds latency, so keep it O(top-k).
Real-time features → huge for responsiveness, but expensive streaming infra; batch if you can't.
The core tension: short-term engagement (CTR) vs. long-term retention & integrity. Every design choice — negative objectives, value weights, guardrail metrics — exists to manage it.
9. Summary + related articles
- Feeds are candidate generation → multi-task ranking → re-ranking, inside a ~200 ms budget.
- The ranking model predicts many engagement heads (like/comment/share/dwell + negatives like hide) via multi-task learning / MMoE.
- The value model
V = Σ wₖ·pₖcollapses them into one sortable score; weights encode business strategy and are tuned by A/B tests against retention guardrails, not raw CTR. - Calibration (Platt/isotonic, watch ECE) is mandatory because the value model sums probabilities.
- Re-ranking adds diversity (caps/MMR/DPP), freshness, and integrity; serve by precomputing embeddings and batching; evaluate with NDCG/AUC offline + A/B online.
Related: Recommendation Systems · Search Systems · Fraud Detection · ML Inference Systems · Common ML System Design Interview Questions
Resources
- Facebook News Feed ranking — https://engineering.fb.com/2021/01/26/ml-applications/news-feed-ranking/
- Twitter/X open-source algorithm — https://github.com/twitter/the-algorithm
- Multi-gate Mixture-of-Experts (MMoE, Ma et al. KDD'18) — https://dl.acm.org/doi/10.1145/3219819.3220007
- ByteByteGo ML System Design (feed) — https://bytebytego.com/courses/machine-learning-system-design-interview/personalized-news-feed