Design a news feed (Facebook)
The interviewer asks
“Design the home news feed for a social network like Facebook. Users see a ranked feed of posts from friends, pages and groups they follow, and new posts should appear within seconds.”
What a strong answer looks like
A strong answer splits the feed into three problems: writing posts, building each user's candidate list, and ranking. Candidate generation uses hybrid fan-out: on write, push the post ID into the feed caches of the author's friends (cheap for normal users), but for celebrities and big pages, pull their recent posts at read time and merge. Feeds are stored as short lists of post IDs in Redis or a wide-column store; post bodies, author data and counters are hydrated from caches at read time. Ranking scores a few hundred candidates with an ML model (predicted likes, comments, dwell time) and applies diversity rules. Deep dives: fan-out choice with numbers, the feed storage layout, ranking at read time under a latency budget, and pagination with a stable cursor. Scale: 2B DAU, ~100k feed reads/s at peak per region, p99 under ~300 ms.
Concepts to mention
1 Requirements
Functional
- Users create posts with text, images or video links
- Home feed shows posts from friends, followed pages and groups, ranked by relevance
- Infinite scroll with pagination and no duplicates while scrolling
- New posts from friends appear in the feed within a few seconds
- Show like and comment counts and the viewer's own reaction on each post
- Respect privacy settings and blocks at read time
Non-functional
- Scale: 2B monthly users, 1B DAU, each opens the feed ~10 times a day
- Feed load latency: p50 < 150 ms, p99 < 400 ms server side
- Availability: 99.95%; a degraded feed (less ranked, cached) is better than an error
- Freshness: friend posts visible within ~5 s (eventual consistency is fine)
- Posts must never be shown to users who are not allowed to see them
- Write path must handle authors with 100M followers
Out of scope (say it out loud)
- Ads insertion and auction
- Media upload and transcoding pipeline
- Comments and messaging systems in detail
2 Estimates
3 API
| Endpoint | What it does |
|---|---|
POST/v1/posts | Create a post: text, media_ids, audience (public/friends/custom); returns post_id |
GET/v1/feed?cursor={cursor}&limit=20 | Get the next page of the ranked home feed; returns posts and a cursor |
POST/v1/posts/{id}/reactions | Like or react to a post |
POST/v1/feed/seen | Batch report of impressions and dwell time used by ranking and to avoid repeats |
GET/v1/feed/new-count?since={ts} | Lightweight check for the 'new posts' pill (or pushed over an existing connection) |
4 Data model
postsSharded MySQL / TAO-style graph store, cached in Memcachedpost_id (PK, Snowflake ID)author_idtextmedia_refsaudiencecreated_atdeletedSharded by author_id so a user's posts are together. Snowflake IDs sort by time, which helps merging.
social_graphGraph store (TAO-like) over sharded MySQL + cache(user_id, edge_type, target_id, created_at)Edges: friend, follows_page, member_of_group. Both directions stored for fan-out (who follows me) and reads (whom do I follow).
feed_inboxRedis Cluster (hot) / Cassandra (warm)key feed:{user_id} → sorted set of (post_id, score=created_at)capped at ~500-1000 entriesOnly IDs, not bodies. Partitioned by user_id. Inactive users' inboxes expire and are rebuilt by pull on next login.
celebrity_outboxRedis / Memcachedoutbox:{author_id} → latest ~200 post_idsFor high-follower authors whose posts are pulled at read time, not fanned out.
countersRedis + sharded counter servicepost_id → like_countcomment_countshare_countApproximate, batched counters; exact numbers not needed in feed.
seen_impressionsRedis (short TTL) + Kafka to warehouseuser_id → bloom filter or set of post_ids seen in last 48 hAvoid showing the same post again; also training data for ranking.
5 Architecture
Hover a box to highlight its connections.
6 Request flows
Publish a post
- 1User posts a photo caption; Post Service validates it, assigns a Snowflake post_id and writes it to the posts DB (sharded by author)
- 2Post Service publishes PostCreated(post_id, author_id, audience) to Kafka and returns success; the author sees it immediately in their own timeline
- 3Fan-out workers consume the event and fetch the author's friend/follower list in pages of ~1,000 from the graph service
- 4If the author has fewer than ~10k followers, workers ZADD post_id into each follower's feed inbox (only for followers active in the last ~30 days)
- 5If the author is a celebrity or big page, workers skip fan-out and only append the post to the author's outbox
- 6Inboxes are trimmed to the newest ~500-1,000 IDs; a 'new posts' signal is sent to online followers
Load the feed
- 1Feed Service reads ~500 candidate IDs from feed:{user} in Redis
- 2It fetches the list of celebrities/pages the user follows (cached) and pulls their recent post IDs from outboxes, merging them into the candidates
- 3It filters out seen posts, deleted posts and posts the viewer may not see (privacy, blocks)
- 4Ranking Service scores the remaining ~300 candidates using user, author and post features (light model first, heavier model on the top ~100)
- 5Feed Service applies diversity rules (not 5 posts from one page in a row) and takes the top 20
- 6It hydrates bodies, author names, counts and the viewer's reaction from Memcached in one multi-get
- 7Response returns the posts and a cursor; the ranked list for this session is cached for ~10 minutes so next pages are stable
7 Deep dives
Fan-out on write vs on read (hybrid)
Fan-out on write (push) copies each new post ID into every follower's inbox. Reads become a single fast list read, which matters because reads outnumber writes ~20:1. But a page with 100M followers would cause 100M writes per post and delay delivery for minutes. Fan-out on read (pull) stores posts only with the author and merges at read time: no write amplification, but a user following 500 sources needs 500 lookups per feed load. The hybrid used in practice: push for normal authors (say < 10k followers), pull for celebrities and big pages, and merge both at read time. A user typically follows only a handful of celebrities, so the read-time merge touches maybe 10-50 outboxes, which are hot in cache anyway. Two more optimisations: skip fan-out to inactive users (no login in 30 days) and rebuild their feed by pull when they return, and do fan-out in priority order (online users first). Numbers to say: ~1.2M inbox inserts/s is fine for a Redis cluster of a few hundred nodes; 100M inserts for one post is not.
Ranking under a latency budget
Chronological feeds are simple but users see low-value posts. Ranked feeds score each candidate by predicted engagement: P(like), P(comment), P(share), expected dwell time, P(hide), combined with weights into one score. The work must fit inside ~100 ms. So ranking is staged: candidate generation (~500 IDs from inbox + outboxes + recommendations), a cheap first-pass model (logistic regression or small tree model) to cut to ~100, then a heavier neural model on the top ~100, then business rules: diversity, freshness boost, demotion of clickbait, integrity filters. Features come from a feature store: user features (precomputed daily and updated in streaming), author affinity (how often this user interacts with this author), and post features (age, early engagement velocity from Flink counters). Scoring is batched on GPU or optimised CPU inference servers. If ranking times out, the feed falls back to the inbox order with a simple heuristic, so the user still gets a feed. Logging impressions and outcomes back to Kafka closes the training loop.
Feed storage and pagination
Store only IDs in the inbox; never copy post bodies into millions of inboxes, because edits and deletes would have to touch every copy. A Redis sorted set per user with score = time (or Snowflake ID) and a cap of ~500-1,000 entries keeps memory at ~4-8 KB per user. For users beyond the cap, deeper history is pulled from authors' posts tables. Pagination is tricky with ranking: if you re-rank on every page, new posts shift positions and users see duplicates or miss posts. Solution: on the first request, rank a larger batch (e.g. 200), store that ordered list in a session cache keyed by a feed session ID, and serve pages from it using an opaque cursor (session_id + offset). New posts that arrive while scrolling are not inserted mid-list; they show up as a 'new posts' pill and are added at the top on refresh. Seen-post tracking (a per-user Bloom filter for the last 48 h) stops the same post from reappearing across sessions.
Privacy and deletes at read time
Fan-out happens before the reader asks, but permissions can change: a user unfriends, blocks, or the author changes the audience or deletes the post. So the inbox is only a candidate list, and every candidate is checked at read time: is the post deleted, is the viewer in the audience, has either side blocked the other. These checks use cached graph edges and post metadata in one batched multi-get, so they add a few ms. On delete, we do not chase every inbox; we mark the post deleted and the read-time filter drops it, and the inbox entry ages out. This also makes the system safe against fan-out bugs: a wrong inbox entry can never leak a private post, because authorisation is enforced on the read path, not on the write path.
8 Trade-offs
Push vs pull vs hybrid
Push: fast reads, heavy writes, wasted work for inactive users, celebrity problem. Pull: cheap writes, slow and expensive reads. Hybrid gives fast reads for nearly everyone with bounded write cost. Choose hybrid with a follower threshold, and say the threshold is tuned by measuring.
Chronological vs ranked
Chronological is transparent, cheap and easy to paginate. Ranked improves engagement but needs ML infrastructure, makes pagination harder, and raises fairness questions. Many apps offer both (ranked default, 'Most recent' option).
Redis vs Cassandra for inboxes
Redis gives sub-ms reads but RAM is expensive at TB scale. Cassandra is cheaper per GB and durable but has higher tail latency. Common answer: Redis for active users with a cap, rebuild from source for the rest; or a tiered store.
9 Bottlenecks
Celebrity post storms
A film star posts during a big release and 50M followers open the app. Pull model reads the same outbox millions of times: replicate hot outboxes across cache nodes and use local in-process caches in Feed Service for the top few thousand authors.
Fan-out lag during peak
On New Year's Eve post volume spikes and fan-out workers fall behind. Scale workers on Kafka lag, prioritise online recipients, and allow inactive recipients to be skipped entirely.
Ranking cost
Scoring 300 candidates for 350k requests/s is expensive. Cache ranked sessions, use a cheap first-stage model, and reduce candidates for users who scroll little.
10 Failure handling
Ranking service down or slow
Feed Service has a strict timeout (~80 ms) for ranking and falls back to a heuristic order (recency × affinity from cache). Users get a slightly worse feed, not an error.
Redis inbox shard lost
Inboxes are a cache. Affected users get their feed rebuilt by pull: read the followed-authors list and merge recent posts. Slower for a few minutes, then inbox is repopulated.
Fan-out worker crashes
Kafka redelivers the event from the last committed offset. ZADD of the same post_id is idempotent, so replays do not duplicate entries.
11 What impresses
- Doing the fan-out maths to justify the hybrid threshold
- Storing only IDs and enforcing privacy on the read path
- Explaining staged ranking with a latency budget and a fallback
- Stable pagination through a cached ranked session
- Skipping fan-out for inactive users and rebuilding on login
- Mentioning the feedback loop: impressions to training data
12 Mistakes
- Running a giant SQL JOIN of follows and posts on every feed load
- Pure push with no plan for celebrities
- Copying full post bodies into every follower's feed
- Ignoring ranking entirely or hand-waving 'ML ranks it'
- Offset-based pagination that shows duplicates as new posts arrive
- Checking privacy only at write time
13 Follow-ups
Lessons this design uses
Saved on this device. Create a free account to keep it on every device.
Practise this with Sam
A real mock interview, out loud, with an honest scorecard. Free to start.