Case Study: News Feed¶
Step 1: Requirements¶
Functional: a user posts content; a user views a feed composed of posts from everyone they follow, most-recent-first (ranking beyond chronological order is a real extension, but this case study covers the chronological core).
Non-functional: feed loads must be fast (sub-200ms is a common target — a slow feed load is one of the most visible latency complaints in any social product); read-heavy by a wide margin (users scroll feeds far more often than they post); eventual consistency is acceptable (a post appearing in followers' feeds a few seconds late is unnoticeable; a feed load timing out is not).
Step 2: Capacity estimation¶
A useful number to compute upfront: average follower count, since it directly determines how expensive "notify everyone who follows this user" is. At 200 average follows per user and 100M DAU, one post potentially needs to reach a lot of feeds — but averages hide the actual shape of the problem, which shows up in step 4.
Step 3: High-level design¶
Step 4: Deep dive — fan-out on write vs. fan-out on read¶
This is the central design decision for any feed system, and the two approaches have opposite trade-offs:
Fan-out on write: when a user posts, the fan-out service immediately pushes the new post's ID into the precomputed feed cache of every follower. Reading a feed is then just reading one already-assembled list — fast, exactly matching the read-heavy, latency-sensitive requirement from step 1. The cost is paid at write time instead: a user with 10 million followers triggers 10 million feed-cache writes for a single post — the celebrity problem, where fan-out on write becomes disproportionately expensive exactly for the accounts that post to the largest audiences.
Fan-out on read: posting is cheap (write once); reading a feed means querying every followed user's recent posts and merging them at request time — expensive when a user follows many people, and it pays this cost on every single feed load, not once per post.
Step 4 (continued): the hybrid solution¶
Push for ordinary accounts (the vast majority, where fan-out cost is small), and skip the push for accounts above a follower-count threshold — instead, pull only those celebrity accounts' recent posts directly at read time and merge them into the precomputed feed. This bounds the worst case on both sides: no single post ever triggers millions of writes, and no feed read ever needs to pull from more than a small number of celebrity accounts, however many total accounts a user follows.
Step 5: Bottlenecks and trade-offs¶
The feed cache is the system's core piece of state and needs the same cache-aside reasoning applied elsewhere — but here it's populated by the fan-out service (write-driven), not lazily on a cache miss, since the whole point is having it ready before the read arrives. Storage for precomputed feeds grows with (users x feed length kept), which is a deliberate trade of storage for read speed — exactly the kind of trade horizontal scaling is meant to absorb, since feed cache storage is easy to shard by user ID with no cross-shard queries needed.
Common pitfall¶
Applying fan-out on write uniformly, without a celebrity exception, looks correct at small scale (in testing, nobody has 10 million followers) and falls over specifically at the scale where it matters most — a single post from a very popular account can generate a burst of feed-cache writes large enough to degrade the whole system's write capacity for everyone else, at the exact moment (a popular post going out) the system most needs to stay responsive.