The Runtime Theory
hardApplicationDSA#system-design#distributed-systems#fanout

Design a news feed

The classic fanout interview: whether you can compute the write-amplification math, find the hybrid line, and keep reads fast without keeping everything in memory.

The Runtime Theory Team2 min readasked at meta · twitter · linkedin · google

A news feed is one decision — fanout-on-write vs fanout-on-read — and everything else follows from the arithmetic. Interviewers probe whether you can compute the amplification and find the hybrid.

Requirements and capacity

200M DAU; average user follows 300 accounts; 500M posts/day (~5.8k writes/s, 3x peak); each DAU loads their feed ~10x/day → 2B reads/day ≈ 23k QPS. p99 feed load <300ms. Post storage: 500M/day × ~1KB ≈ 500GB/day → ~180TB/year — cheap object/key-value storage, not the interesting part. The interesting part is the timeline cache.

The one decision

Fanout-on-write: each post is inserted into every follower's timeline at publish time. Cost per post = follower count. Average 300 → 1.7B timeline writes/day ≈ 20k writes/s sustained. Reads become one list fetch — fast, but writes are amplified by the follower graph.

Fanout-on-read: store the post once; assemble the timeline by merging the recent posts of each followee at read time. Cost per read = follower count × merge work. Writes stay cheap; reads pay 300 merges each at 23k QPS — that's ~7M merges/s. Unaffordable.

The hybrid (what real systems do): fanout-on-write for users under ~10k followers; celebrities' posts are fetched and merged into the precomputed list at read time. The threshold is where followers × write_cost crosses merge_cost × read_rate — say it as an equation, not a magic number. 99.9% of users get the push path; the 0.1% who would generate more than half the amplification get the pull path.

Data model

posts(post_id, author_id, content, ts, flags); follows(follower_id, followee_id, created_at); timeline:{user_id} → Redis list of post IDs, capped at ~1000. Cache stores IDs, not payloads: IDs are ~8 bytes each and rankable later; payloads come from a post cache on render. Storing payloads in the timeline means every edit re-ranks the list — storing IDs means the feed can change ranking without a rewrite.

Pipeline

Post service → queue → fanout workers → one list insert per follower (batched). The queue decouples publish latency from fanout cost: the author sees their post live while workers drain. Worker lag under a burst is visible as slow timeline refresh for followers, never as a failed publish — say that tradeoff explicitly.

Tradeoffs

Chronological vs ranked: chronological lists are cacheable and cheap; ranked feeds need score recomputation on interaction (like/comment events) — push rank updates through the same queue and re-sort in place. Cold users: compute their timeline on first read, evict by recency — 200M users × 800B of IDs ≈ 160GB only if everything is cached; the cache holds the hot users.

Bottlenecks

Fanout workers during posting peaks; the celebrity read-merge path; timeline cache memory; and write amplification to the DB on the push path. The feed is a memory and queue problem — name that and the architecture answers itself.

This answer walks

Follow-ups they'll push on

  1. 01Where exactly does the line between fanout-on-write and fanout-on-read sit, and how do you find it with data?
  2. 02How do you rank the feed without recomputing every user's timeline on every like?
  3. 03The feed cache holds post IDs, not post payloads. Why, and what does the read path do about it?
  4. 04A user follows 100k accounts. What does their feed cost on write, and on read?

More interviews in this topic

One dispatch a week

The trace behind each question, the tradeoff that explains it, and one technical dispatch per week — no noise.

One technical dispatch per week. No noise.