Part 11 · Design It Yourself: Case Studies
Design a News Feed
A hundred million users opening a feed ten times a day, and one celebrity post that must reach fifty million followers. A full news-feed design: fan-out on write vs on read, the hybrid that handles celebrities, feed caches, hydration, ranking, pagination and counters.
On this page
The Problem
Open Instagram, X (Twitter), LinkedIn or Facebook, and within a second you see a personalised feed. A feed is a list of posts from the people you follow. The newest (or most relevant) posts come first. Each post has photos, like counts and comments. The list keeps loading as you scroll.
Behind that simple screen is one of the hardest problems in system design:
- Hundreds of millions of users, each following hundreds of accounts.
- Some accounts have tens of millions of followers.
- Every feed is different, and must load in well under a second.
- New posts should appear quickly (seconds, not hours).
The simple approach is: find everyone I follow, get all their recent posts, sort them, and show the top 20. This works for 1,000 users. At 100 million users, it fails. This case study is about the main trade-off (a choice where you gain one thing and lose another) in feed design: do the work when someone posts, or when someone reads?Doing work when someone posts is called fan-out on write. Fan-out means copying one post out to many followers. Doing the work when someone reads is called fan-out on read.
The Core Idea
Imagine a newspaper delivery service in a big city.
Option 1: deliver to every doorstep (fan-out on write, also called push). Push means the system sends the work out ahead of time. When a newspaper is printed, delivery staff put a copy in every subscriber's letterbox. Reading is instant: you open the letterbox. But if a newspaper has 50 million subscribers, printing one edition means 50 million deliveries.
Option 2: everyone collects from the newsstand (fan-out on read, also called pull). Pull means the reader fetches the data when they need it. Newspapers stay at the newsstand. Each reader goes and collects the papers they subscribe to, and puts them together. Printing is cheap. But every reader does more work, and a reader who subscribes to 500 papers has a long, slow trip.
Option 3: the hybrid. A hybrid mixes both options. Deliver local newspapers (normal accounts) to doorsteps. For the huge national papers (celebrities), readers pick them up from the newsstand and combine them with what is in their letterbox.
Real feeds use the hybrid.
How It Works
Step 1: Requirements
Functional:
- Users can publish posts (text, images, video).
- Users can follow other users.
- Users see a home feed of posts from accounts they follow, paginated with infinite scroll.
- The feed order is reverse-chronological, or ranked by relevance.
- Show like and comment counts and whether I liked each post.
Out of scope: writing comments, search, ads, stories, direct messages (post 63), and the details of recommendation models.
Non-functional:
- Fast reads: the feed loads in < 500 ms (p99), because this is the app's main screen. p99 means 99 out of 100 requests are at least this fast. Only the slowest 1 in 100 may take longer.
- Freshness: new posts appear in followers' feeds within seconds to a minute.
- Highly available: a slightly stale feed is much better than an error (post 27, choose availability).
- Read-heavy: far more feed views than posts.
- Scalable to hundreds of millions of users, including celebrity accounts.
- Eventual consistency is acceptable for feeds and counts (post 28). Eventual consistency means different copies of the data may differ for a short time, but they all become equal soon.
Step 2: Estimation
Assumptions:
- 100M daily active users (DAU). DAU is the number of different people who use the app on one day. Each opens the feed 10 times per day.
- 10% of DAU post once per day, which is 10M posts per day.
- Average followers per poster: 200. A small number of accounts have 10M–100M followers.
- Each feed page shows 20 posts.
So this means:
- Reads dominate (about 100× the posts), so precomputing feeds is attractive.
- Fan-out on write is manageable for normal users (tens of thousands of cache writes per second), but celebrities break it (50M writes for one post).
- The feed cache (lists of IDs) is around a terabyte. That needs a sharded Redis cluster. Redis is a fast database that keeps data in memory. Sharded means the data is split across many machines. This is affordable if we only keep feeds for active users.
- Media (photos and video) must go through object storage + CDN (posts 14 and 17). Object storage keeps big files cheaply. A CDN (content delivery network) keeps copies of files close to users.
Step 3: API and data model
API:
POST /v1/posts { text, mediaIds[] } → 201 { postId }POST /v1/users/{id}/follow → 204GET /v1/feed?cursor=…&limit=20 → 200 { "items": [ { post, author, likeCount, commentCount, likedByMe } … ], "nextCursor": "…" }Use cursor-based pagination (post 34). Pagination means sending a long list in small pages. A cursor is a marker that says "continue after this item". New posts arriving at the top must not cause duplicates or gaps while you scroll.
Data model:
users: user_id, name, avatar_url, follower_count, is_celebrity (derived flag)posts: post_id (Snowflake: time-ordered), author_id, text, media_refs, created_at → stored in a sharded DB / wide-column store, sharded by post_id or author_idfollows: (follower_id, followee_id, created_at) → two access patterns: "who do I follow?" and "who follows X?" → often stored twice (by follower and by followee) = denormalised (post 23)feed_cache: user_id → sorted list of (post_id, score/timestamp) [Redis sorted set / list]counters: post_id → like_count, comment_count [Redis + async DB]likes: (post_id, user_id) → exists [for "likedByMe"]The social graph is the web of who follows whom. It can live in a sharded relational or wide-column database, or in a dedicated graph store (post 20). Facebook built TAO for exactly this.
Step 4: High-level design
- •Client → post service → posts DB (source of truth)
- •PostCreated event → Kafka
- •Fan-out workers look up followers, skip celebrities
- •Push the post ID into each follower's feed cache (Redis)
- •Read my precomputed post IDs from the feed cache
- •Pull recent posts from celebrities I follow
- •Merge and rank → top 20
- •Hydrate in batches: posts, authors, counters, likedByMe
- •Return the page + cursor; media from the CDN
- The write path is fast for the author. The post is saved, an event is published to Kafka (a system that stores a stream of events in order), and the "posted!" response returns immediately. Fan-out happens asynchronously, which means in the background, after the reply (posts 18 and 39).
- The read path is mostly cache reads: a list of IDs, then hydration (turning IDs into full objects, such as the post text and author name) from caches, in batches (many items in one request).
Step 5: Deep dives
Deep dive 1: Fan-out on write (push)
When a user posts, write the post ID into every follower's feed cache:
Asha posts (200 followers) → 200 × ZADD feed:{follower} <timestamp> <post_id>- ✅ Very fast reads. A feed is just "read my list", and reads are ~100× more frequent than writes.
- ✅ Simple read path, and a predictable read latency.
- ❌ Celebrity problem: 50M followers means 50M writes. These take minutes and flood the system. A "hot key" is one piece of data that gets far more traffic than the rest. Write amplification means one action causes many writes.
- ❌ Wasted work: many followers are inactive and will never read those feed entries.
- ❌ Slower "publish to all followers" for big accounts.
Deep dive 2: Fan-out on read (pull)
When a user opens the feed, fetch recent posts from everyone they follow, then merge:
Me → follow 300 accounts → fetch latest posts per account (from per-author timelines) → merge → top 20- ✅ Cheap writes, with no fan-out at all. It's always fresh, and no work is wasted on inactive users.
- ❌ Slow, expensive reads: fetching from hundreds of authors for each feed view, at 35,000 views per second, is a huge amount of work. The total time is set by the slowest fetch. This is called tail latency (post 8): when you wait for many calls, the slowest one decides the speed.
Deep dive 3: The hybrid, which is what real systems do
- +Reads are one fast cache lookup
- −A 50 M-follower post means 50 M writes
- −Wasted work for inactive followers
- +Writes are cheap — one row
- −Every feed open merges hundreds of timelines
- +Push for normal authors, pull for celebrities
- +Merge both at read time
- Most authors are normal, so most feed content is precomputed (fast reads).
- Each user follows only a handful of celebrities, so pulling their recent posts (which are heavily cached, because everyone reads them) is cheap.
- Inactive users: skip fan-out to users who haven't opened the app for, say, 30 days, and rebuild their feed on demand (pull) when they return.
Deep dive 4: The feed cache
- Redis sorted sets keyed by user:
feed:{user_id}. A sorted set is a Redis data type that holds unique items, each with a number called a score. Redis keeps the items ordered by score. Here the score is the timestamp (or a rank score), and the item (the "member") is the post_id. - Keep only the latest N (for example, 500–1,000) entries per user. Trim the list on each insert with
ZREMRANGEBYRANK, a Redis command that removes items by their position in the order. Older content is fetched from storage if someone scrolls very far. - Store IDs, not full posts. It's small, and edits or deletions are reflected when hydrating.
- Shard by user_id across a Redis cluster (post 25). Each user's feed lives on one shard. Add replicas (extra copies) so the feed stays available if a machine fails (post 24).
- Cache miss or rebuild: if a user's feed cache is empty (new user, eviction or returning inactive user), build it by pulling, then store it.
Deep dive 5: Hydration: turning IDs into a page
For 20 post IDs, we need post content, author names and avatars, like and comment counts, and "did I like this?":
- Batch fetch everything (multi-get from caches), never one by one. The N+1 problem happens when you make 1 call for the list and then N more calls, one for each item. It is slow (posts 5 and 32).
- Layered caches: a post cache (hot posts are read by millions), a user or profile cache, and counters in Redis.
- "Liked by me": check a set of (post_id, user_id) likes, cached or in a fast key-value store.
- Parallel calls (all at the same time) with timeouts (a limit on how long to wait). If counters are slow, show the post without counts instead of failing (post 44).
Deep dive 6: Ranking
Many feeds are ranked. Ranking means sorting posts by how useful they are to you, not only by time:
- 1Candidate generation~500 recent posts from the feed cache + celebrity pulls (+ recommendations)
- 2Feature lookupauthor affinity, post age, engagement so far, media type, my history
- 3Scoringan ML model predicts the chance I'll like, comment or share
- 4Re-ranking rulesdiversity, freshness, remove already-seen posts, integrity filters
- 5Return top 20 + cursor
- Ranking runs at read time on a limited set of candidates, which keeps it fast.
- Pagination with ranked feeds needs care. Store the ranked session (or a snapshot of the candidates) so page 2 matches page 1. Use an opaque cursor, which is a token that the client cannot read or change.
- Keep a chronological option where users want it.
Deep dive 7: Counters, likes and consistency
- Like counts are updated asynchronously. Redis collects the increments in batches and saves them to the database every so often. This is called write-behind (post 16). Counts may lag a little, which is acceptable (post 28).
- Very hot posts (viral content) are hot keys. Use sharded counters (split one counter into several keys, and add them up when you read) and local caching (post 16).
- Read-your-writes for my own actions: after I like a post, I must see my own change right away. So my view shows it as liked immediately, even if the global count updates a few seconds later (post 24). Two things help here. Optimistic UI means the app shows the result before the server confirms it. The app also reads my own like state from the source of truth.
Deep dive 8: Edits, deletes, privacy and unfollows
- Deleted posts: because feeds store IDs, hydration simply skips posts marked deleted. There's no need to remove the ID from millions of feed caches immediately (a cleanup job can do that later).
- Privacy changes and blocks: check visibility at read time during hydration (a post author blocked me, or an account turned private). Never rely only on what was fanned out earlier.
- Unfollow: stop future fan-out, and filter that author's posts at read time (optionally clean the cache asynchronously).
- New follow: optionally backfill a few recent posts from the new account into the follower's feed. Backfill means adding old items that were missed.
Deep dive 9: Reliability and performance
- Availability over consistency: if fan-out is delayed, feeds are slightly stale, which is fine. If the ranking service fails, fall back to chronological. If counters fail, show posts without counts (post 44).
- Queue-based fan-out absorbs bursts (for example, everyone posting during a big match). A queue holds work until a worker is free. Monitor fan-out lag (how far the workers are behind, called consumer lag in Kafka, post 36) as a key freshness measure.
- Multi-region: users read from their nearest region's caches. Posts and events replicate across regions asynchronously (post 24).
- Prefetching: the app can fetch the next page in the background and cache the first page locally for instant app opens.
- SLOs (service level objectives, which are goals you promise to meet): feed p99 latency, fan-out delay ("99% of posts in followers' feeds within 60 s"), and error rate (post 47).
Wrap-up
- Key decisions:
- Hybrid fan-out: push for normal users, pull for celebrities, and skip inactive users.
- Feed caches of post IDs in a sharded Redis cluster.
- Async fan-out through Kafka.
- Batched hydration from layered caches.
- Read-time ranking over a candidate set.
- Visibility checks at read time.
- Cursor pagination.
- Eventually consistent counters.
- Trade-offs: faster reads (push) vs cheaper writes (pull); freshness vs cost; ranking relevance vs predictability; eventual consistency for counts and feeds.
- Next steps: recommendation-driven content from accounts you don't follow, ads insertion, video-heavy feeds, and real-time "new posts" banners via SSE or WebSockets (post 33). Both are ways for the server to push updates to the browser.
Trade-offs
| Fan-out on write (push) | Fan-out on read (pull) | Hybrid | |
|---|---|---|---|
| Read latency | ✅ Very fast | ❌ Slow (many fetches) | ✅ Fast |
| Write cost | ❌ High (× followers) | ✅ Minimal | 🟡 Moderate |
| Celebrity accounts | ❌ Breaks (millions of writes) | ✅ Fine | ✅ Handled by pull |
| Inactive users | ❌ Wasted work | ✅ No waste | ✅ Skip + rebuild on return |
| Freshness | 🟡 Delay = fan-out lag | ✅ Always fresh | 🟡 Mostly fresh |
| Complexity | 🟡 Medium | 🟡 Medium | ❌ Highest |
In the Real World
Twitter's timelines. In a well-known 2012 talk at QCon San Francisco, "Timelines at Scale", Twitter engineer Raffi Krikorian explained that Twitter precomputed home timelines in Redis. Each tweet was fanned out to followers' timelines (lists of tweet IDs), because reads far outnumbered writes. For accounts with very large follower counts, fanning out to everyone was too slow, so their tweets were merged in at read time. This is the hybrid model described above.
Facebook's TAO and News Feed. Facebook built TAO, a distributed, cached data store for the social graph (users, friendships, likes and posts), to serve the enormous number of graph reads behind News Feed and other features (paper at USENIX ATC 2013). News Feed ranking has evolved from simple formulas into large machine-learning systems that score thousands of candidate stories per user.
Instagram's move to a ranked feed. In 2016, Instagram switched from a purely chronological feed to a ranked one, explaining that people were missing a large share of posts from accounts they followed. Years later, it again offered chronological "Following" views alongside the ranked feed, a sign that ranking is both a technical and a product decision.
LinkedIn and Pinterest. LinkedIn and Pinterest have both written about their feed architectures: candidate generation from followed sources, ranking models, and precomputed or partially materialised feeds, with extensive caching to meet latency targets at scale.
Big live events. During global events (a World Cup final, an election night, a cricket final), social platforms see huge spikes in posting and reading at the same moment. Queue-based fan-out, aggressive caching and graceful degradation (like temporarily simplifying ranking) keep feeds working when it matters most.
Interview Questions
Q1Fan-out on write or fan-out on read?
Fan-out on write (push) builds each follower's feed ahead of time. Reads are then one cheap cache lookup. But a celebrity post creates millions of writes, and work is wasted on inactive users. Fan-out on read (pull) keeps writes cheap. But every time someone opens the feed, the system must merge many timelines. Most systems use a hybrid: push for normal authors, pull for celebrities, and merge both at read time.
Q2How do you store the precomputed feed?
As a capped list or sorted set of post IDs for each user in Redis. It holds just IDs and scores, so it is small. At read time, I hydrate the full post, author and counter objects in batches from their own caches. Inactive users' feeds can expire and be rebuilt when they come back.
Q3How do you paginate a constantly changing feed?
With cursor (keyset) pagination. The cursor is based on the last item's score or timestamp, plus its ID. I do not use offsets (page numbers), because new posts at the top would shift the list. With a cursor, the user never sees duplicates or skipped posts while scrolling.
Q4How do like and comment counts stay fast?
Keep counters in Redis and increase them atomically (as one step that cannot be split). Save them to the database in the background. Show them as eventually consistent, which means they may be slightly old, or approximate for huge numbers. Read likedByMe from a per-user set, which is a quick "is this item in the set?" check.
Q5What happens when a user follows someone new?
Optionally backfill a few of that account's recent posts into the follower's feed cache asynchronously; later posts arrive through normal fan-out. On unfollow, filter that author's posts at read time and clean the cache lazily.
Q6How do you rank a feed?
First, collect candidates (recent posts from followed accounts, celebrity posts, recommendations). Next, look up features (facts about each post and the user). Then score each post with a machine learning model that predicts whether the user will like, comment or share. Last, re-rank with rules for variety, freshness and safety, and return one page.
Key Takeaways
- The central feed trade-off: fan-out on write (fast reads, costly writes) vs fan-out on read (cheap writes, slow reads). Real systems use a hybrid: push for normal users, pull for celebrities, skip inactive users.
- Estimate first: reads are ~100× writes, fan-out is ~billions of cache inserts per day, and a ~1 TB feed cache of post IDs fits a sharded Redis cluster.
- Store IDs, not posts, in feed caches. Hydrate in batches from layered caches (posts, users, counters, likes), and degrade gracefully if parts are slow.
- Rank at read time over a candidate set, use cursor pagination (consistent across pages), and check visibility and deletes at read time.
- Accept eventual consistency for feeds and counts (with read-your-writes for the user's own actions), fan out asynchronously via Kafka, and monitor fan-out lag and feed latency.
Further Reading
- System Design Interview by Alex Xu (chapter 11, "Design A News Feed System")
- The System Design Primer on GitHub ("Design the Twitter timeline and search" solution)
- Raffi Krikorian's talk "Timelines at Scale" (QCon San Francisco, 2012)
- The TAO paper, "TAO: Facebook's Distributed Data Store for the Social Graph" (USENIX ATC 2013)
- Instagram Engineering blog posts on feed ranking
- Pinterest and LinkedIn engineering blog posts on their home feed architectures
Published by Jainik · Last updated · How these lessons are made
Keep learning
Part of the study guide
Related series