FB News Feed
Design a feed from posts and a follow graph, with ranking, pagination, deletion, and privacy.
Interview scope and guarantees
Publish posts, follow authors, read a ranked feed, and remove or restrict posts. Separate candidate freshness from authorization: a stale feed may omit a new post, but must not reveal a now-private post. Start with text and media references; media transfer is a separate path.
Capacity worksheet
Assume 10 million daily users open the feed 20 times: 200 million reads/day, about 2,315/s average and 11,574/s at 5× peak. One million posts/day with 200 followers creates 200 million inbox inserts/day. One author with 20 million followers can exceed that daily average in one burst.
Concrete API contract
POST /posts {text,mediaIds,visibility} -> {postId}
POST /follows {authorId}
GET /feed?cursor=... -> {items,nextCursor}
DELETE /posts/{id} -> 202Data model and access paths
posts(post_id PK,author_id,created_at,visibility,version,deleted_at)
follows(follower_id,author_id); UNIQUE(follower_id,author_id); INDEX(author_id,follower_id)
inbox(user_id,sort_key,post_id,source_version); PK(user_id,sort_key); UNIQUE(user_id,post_id)
fanout_tasks(post_id,shard,cursor,state); PK(post_id,shard)
feed_session(session_id,viewer_id,candidate_ids,rank_version,expires_at)Evolve a solution and explain each change
Figure — Three architecture decisions for FB News Feed, including the pressure each introduces.
Step 1: Read on demand
Fetch recent posts from followed accounts and merge by stable ordering. A user following many accounts requires excessive read work.
Step 2: Fan out ordinary authors
Populate follower candidate lists asynchronously when a post is published. A celebrity creates a huge write burst; projections may lag.
Step 3: Use a hybrid feed
Merge cached candidates with high-fanout authors at read time and recheck visibility. Ranking, pagination, and revocation still need defined semantics.
Responsibility overview
Figure — Connected responsibilities for FB News Feed. Trace the authoritative and derived paths separately.
Worked end-to-end scenario
An ordinary author publishes p10. The post transaction stores content and an outbox entry; workers add its ID to follower candidate lists. A celebrity publishes p11, which stays in an author timeline and is merged at read time. A reader page merges and deduplicates these sources, applies visibility, and ranks a bounded candidate set. If a worker replays p10, the candidate key prevents a duplicate. Later the author removes a follower. The old candidate list may retain p10, so access must be checked when content is assembled. A cached candidate is never proof of current permission.
Why these access paths matter
Posts are indexed by author, creation order, and ID. Candidate lists are keyed by recipient and ordered post identity. Follow relationships need both follower and followee access paths for reads and fanout. Use a cursor tied to a ranking snapshot or explicitly tolerate live-feed movement; an offset across a changing ranked list can duplicate or skip items.
Build the baseline first
- Author → post store → publication event.
- Feed reader → followed authors → recent posts.
- Authorization filter → ranking → response.
Evolve the design under load
- Ordinary author → fanout workers → follower inboxes.
- High-fanout author → author timeline → read-time merge.
- Candidate merge → bounded ranker → stable page cursor.
Defend the hardest decision
Choose the push/pull threshold from fanout cost and observed reader activity, not a fixed celebrity label. Pushing to inactive users wastes writes; pulling every author for every read wastes latency. Merge both paths using post ID deduplication. Ranking a bounded candidate set is affordable, but a changing score can cause duplicates or gaps across pages. A session snapshot or stable cursor semantics makes that behavior explicit.
Failure and recovery analysis
Fanout workers fall behind while a celebrity publishes. Keep publication durable, protect read latency, and surface a freshness metric. Deletion events remove projections eventually, but the final response checks current visibility or an authoritative revocation layer. On follow changes, decide whether the next page uses the existing snapshot or starts a new feed session.
Security and privacy boundary
Apply block lists, visibility changes, and tenant boundaries after candidate retrieval. Avoid retaining deleted post text in secondary indexes indefinitely.
Interview follow-ups with reasoning
Question: What if the ranking service fails?
Show answer and explanation
Answer: Serve a chronological fallback from authorized candidates.
Question: How do you rebuild inboxes?
Show answer and explanation
Answer: Replay source events into a new projection generation and compare coverage before switching.
Question: How do you avoid expensive fanout retries?
Show answer and explanation
Answer: Checkpoint follower ranges and make (user,post) insertion idempotent.
Operate and verify the design
Feed p99, fanout age, ranking fallback rate and visibility-revocation delay.
Publish a celebrity post during fanout backlog and revoke an older post while readers paginate.
A second scenario to test transfer
An ordinary author has 500 followers and posts twice a day: precomputation creates about 1,000 candidate writes/day. A celebrity has 20 million followers and posts ten times: naive fanout creates 200 million candidate writes/day. Pulling that author’s recent posts during active readers’ requests can be cheaper, especially when many followers are inactive. Explain the resulting read merge cost.
How do you prevent an old feed entry from exposing a post after access is revoked?
Show answer and explanation
Answer: Treat the feed entry as a candidate and validate current visibility before returning content. Also propagate removal events to reduce stale work, but do not rely solely on eventual cache cleanup when the contract requires immediate revocation.
Compare alternatives
| Strategy | Strength | Cost |
|---|---|---|
| Fanout-on-write | Cheap common reads | Amplified writes |
| Fanout-on-read | Cheap author writes | Read-time merge |
| Hybrid | Handles audience skew | Two paths and threshold policy |
| Visibility recheck | Protects stale candidates | Extra read-path work |
A design-changing exercise
Deleting a candidate entry fails after a privacy change. Can the post still appear?
Show answer and explanation
Answer: The serving path must enforce current visibility or a defined bounded permission policy. Projection deletion alone is insufficient protection.
Design workshop: choose hybrid fanout with a calculation
Core flows are publish, follow, fetch a ranked page and revoke visibility. Ads and ML model training are outside the baseline. Assume feed p95 under 200 ms, ordinary post projection within five seconds and current authorization before returning protected content. A missing fresh candidate is acceptable within that bound; a private post exposed to an unauthorized reader is not.
An author with F followers, active-reader fraction a, P posts/day and R feed reads/day per active follower costs roughly F×P candidate writes under push versus a×F×R author-timeline merges under pull. The costs are different operations, so compare measured write and merge cost, not only their counts. With F=20 million, P=10, a=0.05 and R=5, push is 200 million candidate writes/day while pull involves five million active feed-read merges. This motivates pull for that author; thousands of ordinary authors can still use push.
# A bounded candidate merge, before expensive ranking.
pushed = inbox_candidates(viewer, cursor)
pulled = merge_recent_timelines(celebrity_follows(viewer))
candidates = unique_by_post_id(pushed + pulled)
authorized = batch_filter_current_visibility(viewer, candidates)
ranked = rank(authorized, pinned_rank_version)
return freeze_session(ranked[:500], viewer, ttl_minutes=5)A session stores viewer identity, ordered candidate IDs and rank version. The cursor is signed session ID plus offset; it is not an unconstrained timestamp. Pagination advances through the frozen list while rechecking current visibility, topping up from later candidates if privacy removals shorten a page. Expiry returns a reset response. New posts appear on refresh rather than shifting an existing session's page boundaries.
Figure — Push ordinary authors and pull celebrities.
The follower index needs both follower→authors and author→followers access paths. Shard fanout_tasks by post and follower-range; insert a batch and its checkpoint in one projection transaction. Retried batches use unique(user_id,post_id), not a newly generated sort key. Source versions reject stale deletion/republication events. If rank placement changes, update the candidate row rather than create another post identity.
Figure — Recovering a feed without leaking revoked posts.
Hot fanout authors use the pull path, but a reader following many celebrities can still create unbounded merges. Cap the number of timelines queried per request, cache eligible public author slices, fetch recent windows and define graceful freshness degradation. Operate on candidate IDs first; media and body hydration happen after authorization. Measure fanout lag, page fill ratio, stale-version rejection, ACL denials and celebrity merge time separately.
Exercise: p17 is fanout-delivered twice with two event IDs, then deleted, then an old publish event is replayed. How many visible copies should remain?
Show answer and explanation
Answer: Zero after deletion becomes authoritative. Candidate uniqueness handles duplicate insertion; a retained post version/tombstone rejects the old publish. The final ACL/deletion check protects the reader even before all projection cleanup completes.