Design Problems · News Feed

Design a News Feed

Posting and reading a personalized feed at scale: fan-out on write or read, the celebrity problem, timeline caches and media storage.

  • 9 min read
  • 9 practice questions
  • 1 graded problem
On this page
  1. Description
  2. The first version, and why it breaks
  3. The numbers
  4. Fan-out on write
  5. The celebrity problem
  6. Inactive users
  7. Reading the feed
  8. Photos and media
  9. Which approach when
  10. Common mistakes
  11. Check yourself
  12. Key takeaways
  13. Quick revise
  14. Practice

Description

The home feed is the heart of most social apps, and "design a news feed" is one of the most common system design questions. It looks simple - show recent posts from the people you follow - but at scale it forces nearly every big trade-off: doing work when data is written versus when it's read, handling a few users who are millions of times more popular than the rest, caching, queues, and media storage.

In this lesson:

  1. Why building the feed on every read stops working.
  2. The numbers, and what they tell you.
  3. Fan-out on write: building feeds ahead of time.
  4. The celebrity problem, and the hybrid fix.
  5. Inactive users, reading the feed, and storing photos.

The first version, and why it breaks

You're building the home feed for a social app: open it, and you see recent posts from the people you follow. The first version runs a query like this every time someone opens the app:

SELECT p.* FROM posts p
JOIN follows f ON f.followee_id = p.author_id
WHERE f.follower_id = :me
ORDER BY p.created_at DESC LIMIT 20;

That works fine for a few thousand users. Now picture someone who follows 300 accounts. Every time they open the app, the database has to dig through 300 people's posts, sort them and pick the newest 20. Multiply that by hundreds of millions of people opening the app ten times a day, and no database keeps up.

The way out is to stop building the feed when someone asks for it, and build it ahead of time instead.

The numbers

Write these down first. Every decision below follows from them.

  • 300 million daily users × 10 feed opens = 3 billion reads a day, about 35,000 a second, peaking near 100,000 a second.
  • 100 million new posts a day, about 1,200 a second.
  • The average user has about 200 followers. A few accounts have tens of millions.
  • A feed should load in under 300 ms, and a new post may take up to a minute to reach followers.

Reads outnumber writes about 30 to 1, and we're allowed a minute of lag. Both facts point the same way: do the expensive work when a post is written, so reading is cheap.

Fan-out on write

When someone posts, push the post's ID into the timeline of every follower. A timeline is a short, precomputed list per user, kept in a cache such as a Redis list: newest first, capped at a few hundred entries. Opening the feed becomes one cache read, plus fetching the posts by ID.

The pushing happens in the background, so the person posting doesn't wait for it:

event: new post 981

201 Created (fast)

page through followers

push 981 to each follower's timeline

Author posts

Post service

Posts database

Queue

Fan-out workers

Follows

Timeline cache

event: new post 981

201 Created (fast)

page through followers

push 981 to each follower's timeline

Author posts

Post service

Posts database

Queue

Fan-out workers

Follows

Timeline cache

The post service saves the post, publishes a "new post" event and returns right away. Fan-out workers read the event, page through the author's followers, and add the post ID to each follower's timeline. At 1,200 posts a second with 200 followers each, that's about 240,000 timeline writes a second. Spread across many workers and cache nodes, that's routine.

Timelines hold IDs, not copies of posts. If someone edits or deletes a post, you change one row, not millions of timeline entries. The cache stays small, too.

The celebrity problem

Now an account with 50 million followers posts. Fan-out on write turns that one post into 50 million timeline writes: minutes of work for the fan-out workers, repeated with every post, and mostly for followers who won't open the app today. Pushing doesn't work for them.

Fan-out on read does: don't push celebrity posts at all. When someone opens their feed, fetch the recent posts of the few celebrities they follow and merge them into their precomputed timeline. So real systems use a hybrid:

  • Ordinary accounts: fan-out on write.
  • Accounts above a follower threshold (say 1 million): fan-out on read.

1. my timeline IDs

2. recent posts of the celebrities I follow

3. merge, newest first

4. hydrate posts by ID

Open feed

Feed service

Timeline cache

Celebrity posts cache

Merged page of IDs

Post cache

1. my timeline IDs

2. recent posts of the celebrities I follow

3. merge, newest first

4. hydrate posts by ID

Open feed

Feed service

Timeline cache

Celebrity posts cache

Merged page of IDs

Post cache

Read more - where to put the threshold

The threshold is a cost trade-off, not a magic number. Pushing costs one timeline write per follower per post. Pulling costs one extra lookup per feed open for everyone who follows the account. An account with many followers who post rarely but read often is cheaper to pull; a small account is always cheaper to push. Teams tune the threshold by measuring both costs.

Inactive users

Most registered users don't open the app on a given day. Pushing posts into their timelines wastes most of those writes and a lot of cache memory. Skip users who haven't been active for a while (say 30 days), and rebuild their timeline from the posts database when they come back. It's a one-time cost when they log in, and nobody notices.

Reading the feed

  1. Get a page of post IDs from the user's timeline in the cache, using a cursor.
  2. Merge in recent posts from the celebrities they follow.
  3. Hydrate the posts: look them up by ID in a post cache, which sits in front of the database.
  4. Return the page.

If a timeline is missing (the cache evicted it, or a cache node failed), rebuild it from the database. The timeline cache is a speed-up, never the source of truth.

Photos and media

Photos are big blobs, and storing them in database rows makes the database huge and slow. Keep them in an object store such as Amazon S3, and keep only the object key in the database.

  • Let the app upload directly to the object store with a pre-signed URL, so gigabytes never stream through your app servers.
  • Serve photos through a CDN that uses the object store as its origin.
  • Use versioned keys (photos/42/v2.jpg), so the CDN can cache each version for a long time.

Which approach when

SituationApproach
Ordinary account postsFan-out on write into followers' timelines
Account with millions of followers postsFan-out on read: merge their posts in when a feed is opened
Follower inactive for 30+ daysSkip them; rebuild their timeline when they return
Timeline missing from the cacheRebuild it from the posts database
A post is edited or deletedChange the post row; timelines hold only IDs

Common mistakes

  • Building the feed with a join at read time. Fine for a prototype, hopeless at 100,000 reads a second.
  • Fanning out synchronously. The person posting would wait for millions of writes. Use a queue.
  • Pushing celebrity posts like everyone else's. One post becomes tens of millions of writes.
  • Copying whole posts into timelines. Edits and deletes then touch millions of entries.
  • Treating the timeline cache as the only copy. A cache node failure would then lose feeds.

Check yourself

Try to answer before opening each one - recalling beats re-reading.

1. Why do timelines store post IDs rather than copies of posts?

Show answer

An edit or deletion then updates one row instead of millions of timeline entries, and the timeline cache stays small. The cost is a hydration step on read, which a post cache makes cheap.

2. A user follows 3 celebrities and 297 ordinary accounts. How is their feed put together?

Show answer

The 297 ordinary accounts' posts are already in their precomputed timeline (fan-out on write). When they open the app, recent posts from the 3 celebrities are fetched and merged in (fan-out on read).

3. A timeline cache node fails. What happens to the users whose timelines lived there?

Show answer

Their timelines are missing, so their next feed read rebuilds them from the posts database. That read is slower, but it's correct. That's why the cache must never be the only copy of anything.

Key takeaways

  • Build feeds ahead of time: fan-out on write into per-user timelines, in the background through a queue.
  • Timelines hold post IDs. Hydrate them on read from a post cache.
  • Pull celebrities' posts at read time instead of pushing them to millions.
  • Skip inactive users, and rebuild their timelines when they come back.
  • Photos live in object storage, are uploaded directly, and are served through a CDN.
  • The timeline cache can always be rebuilt from the database.

Quick revise

The whole skill on one page, for the night before an interview.

Everything from the lesson and the deep dive on one page. Skim it before a design interview, and open the full lesson for anything that doesn't ring a bell.

The numbers

QuantityEstimateSo...
Feed reads300 M users × 10 = 3 B/day ≈ 35,000/s, ~100,000/s peakReads must be one cache lookup
Posts100 M/day ≈ 1,200/sWrites are cheap enough to do extra work on
Followers~200 average; a few with tens of millionsCelebrities need a different path
FreshnessUp to a minute of lag allowedFan-out can run in the background

Reads ≈ 30× writes and a minute of lag is fine → do the work at write time.

Which approach when

SituationApproach
Ordinary account postsFan-out on write into each follower's timeline (via a queue)
Account with millions of followers postsFan-out on read: merge their recent posts when a feed is opened
Follower inactive 30+ daysSkip; rebuild their timeline when they return
Timeline missing from cacheRebuild from the posts database
Post edited or deletedChange one row - timelines hold only IDs
PhotosObject store via pre-signed upload, served through a CDN, versioned keys

Reading a feed

Timeline IDs (cursor) → merge celebrity posts → hydrate from the post cache → filter → return.

The traps

  • Building the feed with a join at read time.
  • Fanning out synchronously - the poster waits for millions of writes.
  • Pushing celebrity posts like everyone else's.
  • Copying whole posts into timelines - edits touch millions of entries.
  • Treating the timeline cache as the only copy.
  • Offset pagination on a feed that changes while you scroll.

Staff-level points, one line each

  • Timeline memory: 300 M users × 800 IDs × 8 B = 1.92 TB raw → 7.68 TB with 2× overhead and 2 replicas → ~120 nodes at 64 GB. Levers: timeline cap and who gets one.
  • Fan-out at 100,000 writes/s: 200 followers 2 ms, 1 M 10 s, 50 M ~8.3 min. Above ~6 M followers an account can't be pushed within a minute even alone - set the threshold well below that.
  • The threshold is a cost trade-off: push = one write per follower per post; pull = one extra lookup per feed open for each follower.
  • Pulling moves the hot spot: 100,000 opens/s × 5 celebrities = 500,000 lookups/s on a few keys - replicate hot keys, tiny in-process caches (seconds), precompute each celebrity's last N posts.
  • Ranked feeds: the timeline becomes a candidate pool; read time = features (fresh, fast store) → model score → diversity re-rank, within tens of milliseconds.
  • Paginate with a cursor (created_at, post_id); apply the same cursor to both sources when merging and de-duplicate.
  • Filter on read: deleted posts, blocks, unfollows, privacy rules - pages can come back short, so fetch extra IDs.
  • Failures: a 30-minute fan-out outage = ~2.2 M queued posts; at 2× speed, another 30 min to catch up. Coalesce timeline rebuilds after a cache node dies; checkpoint paging through huge follower lists.
  • Twitter's "Timelines at Scale" (QCon): tweet IDs in Redis timelines capped near 800, big accounts treated differently.

Say it in an interview

  • "Reads are 30 times writes and a minute of lag is fine, so I fan out on write through a queue."
  • "Accounts above a follower threshold are pulled at read time - I'd set it from the cost of each path."
  • "Timelines hold IDs and are filtered on read, so a deleted post disappears without touching millions of lists."
  • "The cache is rebuildable from the posts table; it's never the only copy."

Practice

This skill comes with 9 multiple-choice questions that tell you which misconception a wrong answer reveals and 1 graded problem. Your answers are graded and your mastery is tracked, so you'll know when the skill holds up. Running code and saving progress need a free account.

Practice Design a News Feed free

Design a News Feed

HardProSystem design problem

Design the backend of a social news feed: posting, following, and each user's home feed of recent posts from the accounts they follow, with photos. Arrows go from the caller to what it calls; mark an arrow async when the caller hands work off.

The full statement, the examples and the hidden test suite are part of AI Proof Engineers Pro.

Practice Design a News Feed free

No card needed. Lessons, practice questions and easy problems are free forever.

Free plan, no card · 7-day Pro trial · Cancel anytime