Real-World System Design Case Studies

Design a News Feed

18 min Lesson 4 of 10

Design a News Feed

A news feed is the core product surface of platforms like Twitter, Facebook, Instagram, and LinkedIn. When you open the app and see a ranked stream of posts from people you follow, that stream is your feed. At scale — hundreds of millions of users each refreshing every few minutes — delivering that feed cheaply, quickly, and correctly is one of the hardest distributed-systems problems in industry.

This lesson focuses on the single most important architectural decision in feed design: fan-out on write vs. fan-out on read — and how real platforms combine both.

What the System Must Do

Before picking an architecture, frame the requirements:

  • Post: A user creates a post. Everyone who follows them should eventually see it in their feed.
  • Read feed: A user opens the app and gets the ~20 most-recent posts from people they follow, ranked by time (or engagement).
  • Scale assumptions: 300 million daily active users, ~5 million posts per minute, average follow count ~200, some celebrities with 50–100 million followers.
  • Latency target: Feed read < 200 ms at p99. Post write can be eventually consistent (a few seconds' delay is acceptable).
Key insight: Reads massively outnumber writes. A typical social platform sees a 100:1 read-to-write ratio or higher. The architecture must be optimised for cheap, fast reads — even if that means more work at write time.

Option 1 — Fan-Out on Write (Push Model)

When a user posts, the system immediately pushes that post into the feed cache of every follower. Each user has a personal feed mailbox (e.g. a Redis sorted set keyed by user ID). Reading the feed is trivial: fetch the top N entries from your mailbox.

  • Write path: Post → look up all followers → write post ID into each follower's feed cache. This is the "fan-out" step.
  • Read path: Fetch the pre-built feed list → hydrate post details → return to client. O(1) per user.

Pros: reads are extremely fast (single cache lookup); no complex join at read time; works well for users with modest follower counts.

Cons: writing one post by a celebrity with 50 million followers means 50 million cache writes — a "hot key" storm. This is called the celebrity problem. Fan-out jobs must be queued asynchronously, introducing write latency. Storage cost is high: every post is duplicated once per follower.

Fan-Out on Write (Push Model) Author (writes a post) Post Service + Fan-out Queue Fan-out Workers (async) Feed Cache User A mailbox Feed Cache User B mailbox Feed Cache User C mailbox Post DB (source of truth) Follower (reads feed) POST push to all followers read Write path: async fan-out to N mailboxes. Read path: single cache fetch.
Fan-out on write: post is pushed into every follower's feed cache at write time, making reads trivially fast.

Option 2 — Fan-Out on Read (Pull Model)

When a user reads their feed, the system pulls posts on demand: look up all the people the user follows, fetch their recent posts from the Post DB (or a cache), merge and rank them, then return the result. Nothing is precomputed.

  • Write path: Post → write once to Post DB. Done. O(1) regardless of follower count.
  • Read path: Fetch the user's follow list → fan-out N parallel reads to post sources → merge → rank → return. O(N) where N is the number of people followed.

Pros: writes are extremely cheap; no celebrity problem; no storage duplication; feed is always fresh (deleted posts disappear immediately).

Cons: reads are expensive and slow — a user following 2,000 accounts triggers 2,000 sub-queries at read time. For active users who open the app constantly, this amplifies DB load enormously. Hard to keep under 200 ms p99.

Fan-Out on Read (Pull Model) Follower (opens feed) Feed Service merge + rank Follow DB (who do I follow?) Post Cache Author A Post Cache Author B Post DB Author C … N GET feed 1. who to follow? 2. parallel fetch posts Read path: fan-out to N authors on every feed request — expensive at scale.
Fan-out on read: posts are fetched from each followed author at request time and merged. Writes are cheap; reads are expensive.

The Hybrid Approach — What Twitter & Facebook Actually Do

Neither pure strategy works at web scale. Both Twitter and Facebook converged on a hybrid model:

  1. Regular users (small follower count): fan-out on write. Their post is pushed to all followers' feed caches immediately, asynchronously via a queue.
  2. Celebrities (millions of followers): fan-out on read. Their posts are NOT pushed — they are too expensive to fan-out. Instead, they are stored in a dedicated hot cache. At read time, the feed service injects celebrity posts into the user's pre-built feed.
  3. Inactive users: their feed mailbox is not pre-built at all. When they wake up, their feed is assembled on read (they do not generate constant cache-maintenance cost).
Twitter's threshold (historically ~10k followers): above that follower count, a user's posts skip the write fan-out and go to the on-read injection path. This keeps the async fan-out queue from spiking when a celebrity tweets.

Feed Cache Data Structure

The feed mailbox is typically a Redis Sorted Set per user, keyed by feed:{user_id}. Each entry stores a post ID (not the full post) as the member and the post timestamp as the score. Fetching the feed is a single ZREVRANGE feed:{user_id} 0 19 call — O(log N + 20) — followed by a batch MGET on a post-detail cache to hydrate the posts.

Keeping only post IDs (not full content) in the mailbox is important: if post content changes after fan-out (e.g., an edit or deletion), the authoritative post-detail cache is the source of truth. The mailbox is just an ordered pointer list.

Stale feed on follow/unfollow: if a user unfollows someone, their old posts should vanish from the feed. With pure fan-out on write you must backfill — remove those post IDs from the mailbox. This is an expensive operation. Most products accept slight eventual consistency here, or limit how far back the purge goes.

Ranking Beyond Chronological Order

Real feeds are not purely chronological. Facebook and Instagram use ML ranking models that score each post on engagement signals (likes, comments, shares, watch time) combined with freshness. Architecturally this means:

  • The feed service fetches a larger candidate set (e.g. 200 posts).
  • A ranking service scores and reorders them.
  • The top N are returned to the client.

The pre-built mailbox becomes a candidate pool, not the final ranked list. Ranking happens at read time on a small candidate set, which keeps it fast while allowing sophisticated personalisation.

Component Summary

  • Post Service — accepts writes, stores in Post DB, publishes to fan-out queue.
  • Fan-out Workers — async consumers; write post IDs into feed mailboxes for regular users; skip celebrities.
  • Feed Service — handles read requests; fetches mailbox, injects celebrity posts on read, triggers ranking.
  • Post Cache — Redis/Memcached layer in front of Post DB; holds full post objects.
  • Follow Graph Store — graph DB or sharded MySQL table; read-heavy, denormalised for fast lookups.
  • Ranking Service — ML-based scorer; called at read time on the candidate set.
The core trade-off in one sentence: fan-out on write trades storage and write amplification for fast reads; fan-out on read trades read latency and CPU for cheap writes. The hybrid model draws the boundary at celebrity accounts — the only case where write amplification becomes catastrophic.