System Design Case Studies
Designing a News Feed
A news feed is fundamentally a fan-out problem — when someone posts, do you push it to every follower's feed immediately, or make each follower pull and merge posts on read? The answer usually depends on how famous the poster is.
- Fan-out-on-write (push): when a user posts, immediately write it into every follower's precomputed feed — reads become O(1), but a celebrity with millions of followers turns one post into millions of writes
- Fan-out-on-read (pull): a feed is assembled at read time by merging posts from everyone a user follows — no write amplification, but every read does real work and scales badly with follow count
- Hybrid approach: push for ordinary users, pull-and-merge for high-fan-out accounts — the common real-world answer, at the cost of two code paths
- Ranking, rather than plain reverse-chronological order, requires scoring candidate posts at read time — pushing at least part of the work back toward fan-out-on-read regardless of delivery strategy
- Feed storage is typically a precomputed list of post IDs per user in a fast store, hydrated with full post content from a separate content store at read time
- Staleness is usually acceptable — a feed a few seconds behind is fine, one of the clearer cases where eventual consistency is not just tolerable but the entire point
| Push (fan-out-on-write) | Pull (fan-out-on-read) | Hybrid | |
|---|---|---|---|
| Write cost | high — one write per follower | none | high only for ordinary accounts |
| Read cost | low — precomputed | high — merge at read time | low, plus a small merge for celebrities |
| Celebrity problem | a write storm per post | not an issue | solved by routing around push |
List<Post> getFeed(String userId) {
List<String> precomputed = feedCache.get(userId); // from push fan-out
List<Post> celebrityPosts = celebrityFollows(userId).stream()
.flatMap(c -> postStore.recentByAuthor(c).stream()) // merge-on-read
.toList();
return merge(hydrate(precomputed), celebrityPosts);
}