Skip to content
SparkTime, home
System Design From Zero to Interview-Ready
Lessons
1 · System Design Foundations
2 · APIs and Communication
3 · Capacity Estimation
4 · Core Building Blocks
5 · Databases
6 · Distributed Systems
7 · Reliability and Operations
8 · Architecture and Security
9 · Interview Method and Foundation Case Studies
10 · Real-World Case Studies
11 · Large-Scale Case Studies
12 · Interview Readiness
Lesson 35 of 45

Lesson 35 · Real-World Case Studies

Design a News Feed: Fan-Out, Ranking and the Celebrity Problem

Design a home feed that shows posts from accounts a user follows: estimates for posting, reading and fan-out, fan-out on read versus fan-out on write, the hybrid model that handles celebrity accounts, precomputed feed storage, hydration, ranking with a chronological fallback, cursor pagination, deletions and privacy, and what changes at social-media scale.

On this page

Key takeaways

  • A feed can be assembled when it is read (fan-out on read) or precomputed when a post is made (fan-out on write); each fails at a different extreme.
  • Fan-out on write gives fast reads for normal accounts but explodes for celebrities with millions of followers.
  • The hybrid model pushes posts from normal accounts into followers' feeds and pulls celebrity posts at read time, merging the two.
  • Store feeds as capped lists of post ids, hydrate them from caches at read time, and apply privacy and deletion checks during hydration.
  • Ranking should degrade gracefully to chronological order, and every part of the pipeline is eventually consistent except a user's view of their own posts.

Open any social app and the first thing you see is a feed: recent posts from the people you follow, possibly reordered by what the app thinks you will care about. Pixly's feed was mentioned in almost every earlier article as "the hard read path". Here we finally design it.

The core difficulty is a fan-in, fan-out problem. A feed read needs posts from hundreds of accounts. A post must appear in the feeds of everyone who follows its author, which for most authors is a few hundred people and for a few celebrities is many millions. Every design choice in this case study is about where to pay that cost: when someone posts, or when someone reads.

What you will learn

#
  • How to estimate posting, reading and fan-out.
  • Fan-out on read and fan-out on write, and where each breaks.
  • The hybrid model for celebrity accounts.
  • Storing precomputed feeds, and hydrating them at read time.
  • Ranking, with a fallback to chronological order.
  • Pagination, deletions, unfollows and privacy.
  • What changes at social-media scale.

1. The problem

#

Users follow other accounts (one-way, like most photo and microblogging apps). The home feed shows posts from followed accounts, newest or most relevant first, with infinite scrolling. Posts contain text and photos (photo files are already handled by object storage and a CDN, article 10).

2. Clarify the requirements

#

Question

Assumed answer

Impact

Follow model?

One-way follows; no limit on followers; following capped at 5,000

Fan-out shape

Celebrities?

Some accounts have tens of millions of followers

Hybrid model

Ordering?

Ranked feed, with chronological as a fallback and an option

Ranking stage

Freshness?

A new post appears in followers' feeds within about 10 seconds; the author sees it immediately

Eventual consistency, read-your-writes for the author

Feed length?

Users rarely scroll past a few hundred items

Capped feed storage

Private accounts and blocks?

Yes

Privacy checks on read

Ads and recommendations from non-followed accounts?

Out of scope (mention as an extension)

n/a

Functional requirements: create posts; follow and unfollow; read the home feed with pagination; show like and comment counts on each item.

Non-functional requirements

Quality

Target

Feed latency

p99 under 300 ms for the first page

Freshness

Followers see a new post within 10 seconds at p95

Availability

99.95% for feed reads; a degraded feed is better than an error

Correctness

Never show a post the viewer is not allowed to see; deleted posts disappear promptly

3. Assumptions and scale estimation

#

We start with Pixly's numbers from articles 06 and 07, then scale up in section 8.

Code
10 million DAU; 50 million registeredFeed reads: 10 per DAU per day -> 100 million/day, about 1,160/s average, about 3,500/s peakPosts: 2 million/day -> about 23/s average, about 70/s peakAverage followers per posting account: 200 (median much lower; a long tail of large accounts)Fan-out on write (if every post is pushed to every follower):  2 million posts x 200 followers = 400 million feed insertions/day  -> about 4,600/s average, about 14,000/s peakCelebrity: one account with 10 million followers posts once  -> 10 million insertions from a single postFeed storage (precomputed): keep the latest 500 post ids per active user  10 million active users x 500 ids x 8 bytes = 40 GB (about 80 GB with structure overhead)

Conclusions: reads (3,500 per second) need to be fast; fan-out on write is affordable on average (14,000 inserts per second at peak) but the celebrity tail is not; feed storage fits comfortably in an in-memory store.

4. API design

#
Code
GET /v1/feed?cursor=...&limit=20200 {  "items": [    {"post_id": "p_8812", "author": {"id": 77, "name": "Meera", "avatar_url": "..."},     "caption": "Monsoon in Munnar", "media": [...], "like_count": 1203, "comment_count": 48,     "liked_by_me": false, "created_at": "..."}  ],  "next_cursor": "opaque"}POST /v1/posts                          (idempotency key; returns 201 with post_id)PUT  /v1/users/{id}/followers/me        (follow, idempotent)DELETE /v1/users/{id}/followers/me      (unfollow)DELETE /v1/posts/{id}                   (author only)

The cursor is opaque (article 04). For a ranked feed, it identifies a position in a snapshot of the ranked list made for this browsing session, so scrolling does not skip or repeat items as new posts arrive.

5. Data model

#
Code
posts        (post_id PK, time-ordered id; author_id; caption; media keys; visibility; created_at; deleted_at)             index (author_id, created_at DESC)            -> "this author's recent posts"follows      (follower_id, followee_id, created_at)        PK (follower_id, followee_id)followers    (followee_id, follower_id)                     reverse index for fan-outuser_flags   (user_id, is_celebrity, last_active_at, is_private)Feed store (precomputed), one entry per user:  key: feed:{user_id}  value: sorted set of post_id with score = created_at (or a ranking score)  capped at 500 entries; expires if the user is inactive for 30 daysCounts: like and comment counters per post (cached, updated asynchronously, article 09)

A sorted set per user is a natural fit for a feed of ids ordered by time or score: Redis sorted sets keep members ordered by score and support range reads, insertions and trimming efficiently (Redis sorted sets). The precomputed feed is a materialised view (Microsoft's Materialized View pattern describes such precomputed, query-optimised views and their eventual consistency (Materialized View pattern)), so it can always be rebuilt from posts and follows.

Partition keys: posts by post_id (and the author index by author_id); follows by follower_id; followers by followee_id; the feed store by user_id.

6. Baseline architecture: fan-out on read

#

The simplest design builds the feed when it is requested:

Code
GET /v1/feed:  1. followees = SELECT followee_id FROM follows WHERE follower_id = me        (say 200)  2. for each followee: recent posts by author (from the author index or cache)  3. merge by time (or rank), take the top 20  4. hydrate: post details, authors, counts

It is always fresh, wastes no work on inactive users, and handles celebrities trivially (their posts are read like anyone else's). This is fan-out on read (also called the pull model).

7. What breaks first?

#

At 3,500 feed reads per second with 200 followees each, step 2 alone is 700,000 author-timeline reads per second, and a user following 5,000 accounts makes a single feed request touch 5,000 timelines. Latency suffers (tail latency over many parallel reads, article 02) and the post store carries enormous read load for what is the same work repeated: the same popular authors' timelines read over and over.

The alternative, fan-out on write (the push model), moves the work to posting time: when a post is created, insert its id into every follower's precomputed feed. A feed read becomes one range read of feed:{me}, plus hydration. Reads are fast and cheap.

But fan-out on write breaks at the other end:

  • Celebrities: one post from a 10-million-follower account creates 10 million inserts. At a few hundred thousand inserts per second it takes many seconds to minutes, delays everyone else's fan-out, and repeats with every post.
  • Wasted work: many followers are inactive; pushing to their feeds is wasted.
  • Write amplification: storage and network costs scale with followers, not with posts.

Fan-out on read (pull)

Fan-out on write (push)

Work happens

At read time

At post time

Read latency

Higher; many parallel reads

Low; one range read

Write cost

Low

Proportional to followers

Celebrities

Easy

Very expensive

Inactive users

No wasted work

Wasted pushes

Freshness

Always current

Delayed by fan-out lag

Best for

Users who follow few accounts; celebrity authors

Normal authors; frequently read feeds

8. Scaling the design

#

The hybrid model

#

Combine the two:

  • Normal authors (fewer than a threshold of followers, say 100,000): push each new post id into the feeds of active followers (active in the last 30 days).
  • Celebrity authors (above the threshold): do not push. Their posts are pulled at read time.
  • At read time: read the precomputed feed (pushed items) and the recent posts of the celebrities this user follows (usually a small number), merge, rank, and return.
Code
REQUEST PATH (read)Client -> Feed service:   1. ZREVRANGE feed:{me} (pushed post ids, newest first)   2. celebrity followees of me (small list, cached) -> their recent post ids (cached per celebrity)   3. merge -> candidates -> rank (or sort by time) -> page of 20   4. hydrate: multi-get post objects, authors, counts, my-likes; filter deleted and not-allowed   5. return page + cursorASYNC PATH (write)Post service: insert post + outbox event "PostCreated" (article 21)  -> Fan-out workers (if author is not a celebrity):       page through followers (active only), ZADD post_id into each feed:{follower},       trim each feed to 500 entries  -> Celebrity timeline cache updated (if author is a celebrity)

Each celebrity's recent posts are cached once and read by millions of feed requests, so pulling them is cheap: it is a hot key served from cache (with local caches for the very hottest, article 09).

Fan-out workers

#
  • Consume PostCreated events from a stream partitioned by author (article 11).
  • Read the author's followers in pages (for example 1,000 at a time) and write to feeds in batches (pipelining many ZADD calls).
  • Skip inactive followers; their feed is rebuilt on demand if they return (below).
  • Be idempotent: adding the same post id to a sorted set twice is harmless, which makes redelivery safe.
  • Scale with lag: at 14,000 inserts per second at peak, a modest pool of workers keeps fan-out lag within seconds.

Inactive and returning users

#

If a user who was inactive for months opens the app, feed:{me} does not exist. Build it on demand with fan-out on read (the baseline algorithm) for the first request, store the result, and from then on the user receives pushes again. The first load is slower; every later load is fast.

Hydration

#

The feed store holds only ids. Hydration turns 20 ids into a page:

  • Multi-get post objects from a post cache (article 32), falling back to the database for misses.
  • Multi-get author summaries and counts.
  • Compute liked_by_me with one batched lookup.
  • Filter posts that are deleted, from blocked or private accounts the viewer may no longer see, or otherwise not allowed. Fetch a few extra ids to fill the page after filtering.

Hydration happens on every read, so it is the place to enforce rules that must be current (deletion and privacy), while the precomputed list may be slightly stale.

Ranking

#

A ranked feed adds two stages:

  1. Candidate generation: the merged ids (pushed feed plus celebrity posts), a few hundred candidates.
  2. Scoring: compute a relevance score per candidate from features such as recency, the viewer's past interactions with the author, engagement on the post, and content type, using a model served by a ranking service with a strict latency budget (for example 50 ms).

Keep it robust:

  • If the ranking service is slow or down, fall back to chronological order (circuit breaker, article 23). A slightly less relevant feed is far better than an error.
  • Precompute expensive features offline (affinity between users, post quality signals) and keep them in a fast feature store.
  • Snapshot the ranked list per session (store the ordered ids for the session briefly), so pagination is stable while new posts arrive.

Deletions, unfollows and edits

#
  • Deleted posts: marked deleted in posts; hydration filters them immediately. A background job removes their ids from feeds lazily (or never; trimming removes them eventually).
  • Unfollow: future posts are no longer pushed. Existing items from that author can be filtered at read time (check the follow relationship for authors on the page) and cleaned up asynchronously.
  • Privacy changes and blocks: enforced at hydration, so they take effect immediately even though feeds are precomputed.

The author sees their own post

#

Read-your-writes (article 19): when a user posts, insert the id into their own feed synchronously (or merge "my recent posts" into my feed reads), so the author never wonders where their post went, while followers receive it via asynchronous fan-out.

Social-media scale

#

Now suppose 500 million DAU, each reading the feed 15 times a day, and 100 million posts a day:

Code
Feed reads: 7.5 billion/day -> about 87,000/s average, about 260,000/s at peak (factor 3)Posts: 100 million/day -> about 1,160/s average, about 3,500/s peakFan-out (normal authors, average 300 active followers): 3,500 x 300 = about 1 million inserts/s at peakFeed store: 500 million x 500 ids x 8 B = 2 TB (about 4 TB with overhead), sharded by user_id

What changes:

  • The feed store is a sharded cluster (article 32) of a few dozen to a hundred nodes, plus replicas; it may move to a disk-backed wide-column store with a cache in front, keeping only the most recent items in memory.
  • Fan-out at a million inserts per second needs a large, autoscaled worker fleet reading from a partitioned stream, and an aggressive "active follower" filter.
  • The celebrity threshold is tuned by measured cost: pushing to more than N followers costs more than pulling.
  • Ranking becomes a major subsystem with its own feature pipelines, model training and serving; candidate generation also pulls in recommended posts from accounts the user does not follow (an extension beyond this design).
  • Multi-region: feeds are built and stored per region near users; posts and follow graphs are replicated across regions asynchronously, so a post from another region appears a little later.

The final picture

#
Code
Clients -> CDN (media) ; API gateway -> Feed service -> feed store (sharded, by user)                                         |-> celebrity timeline cache                                         |-> ranking service (fallback: chronological)                                         `-> hydration: post cache, user cache, counts cache, likesPost service -> posts DB -> outbox -> stream (partitioned by author)   -> fan-out workers (normal authors -> active followers' feeds)   -> celebrity timeline cache updaterFollow service -> follows/followers stores (sharded by follower and by followee)

9. Failure scenarios and reliability

#

Failure

Effect

Response

Fan-out workers fall behind

Followers see new posts late

Feeds still serve; autoscale on lag; alert if p95 lag exceeds the freshness target

Feed store node lost

Some users' feeds missing

Replicas; otherwise rebuild on read with fan-out on read for affected users

Celebrity posts at peak

Potential fan-out storm

Hybrid model: celebrity posts are pulled, never pushed

Hot celebrity post

One post object read by millions

Post cache with replicas and local caches; CDN for media

Ranking service down

No relevance ordering

Circuit breaker; chronological fallback

Post cache cold after a restart

Hydration hits the database

Request coalescing, warm the most popular posts, load shedding

Stream outage

New posts not fanned out

Outbox keeps events; fan-out catches up; authors still see their own posts

Deleted post still in feeds

Could show removed content

Hydration filters deleted posts on every read

10. Consistency

#
  • Followers' feeds: eventually consistent; a post appears within seconds (fan-out lag).
  • The author's own feed: read-your-writes, synchronously updated.
  • Counts: eventually consistent, updated asynchronously.
  • Privacy, blocks and deletion: enforced at read time, so effectively immediate even though feed lists are stale.
  • Pagination: stable within a session through the ranked snapshot or cursor.

11. Security

#
  • Visibility checks at hydration for private accounts, followers-only posts and blocked users (object-level authorisation, article 28).
  • Spam and abuse: rate-limit posting and following (mass-follow bots), detect spam accounts, and down-rank or remove harmful content.
  • Scraping: rate-limit feed reads per user and IP, cap page sizes and pagination depth.
  • No leakage through counts or ids: do not reveal private accounts' posts through counts or error messages.

12. Observability

#

Signal

Why

Feed latency p50 and p99, by stage (read feed, merge, rank, hydrate)

The main SLO and where time goes

Fan-out lag (post created to inserted in feeds), p95

The freshness SLO

Fan-out insert rate and worker backlog

Capacity

Hydration cache hit ratios and filtered-item rate

Cache health; sudden filtering spikes may indicate bugs

Ranking latency and fallback rate

Ranking health

Rebuild-on-read rate

Inactive users returning, or feed store loss

Engagement metrics (scroll depth, likes per session)

Product health; ranking quality

13. Trade-offs

#

Decision

Chosen

Alternative

Why

Feed assembly

Hybrid: push for normal authors, pull for celebrities

Pure push or pure pull

Fast reads without celebrity fan-out storms

Push targets

Active followers only

All followers

Avoid wasted work; rebuild on return

Feed storage

Capped sorted sets of ids, sharded by user

Store full post objects per feed

Small, cheap, always hydrated with current data

Privacy and deletion

Enforced at hydration

Rewrite feeds on change

Immediate effect without expensive fan-out of removals

Ranking

Separate service with chronological fallback

Ranking inline, no fallback

Relevance without risking availability

Pagination

Session snapshot cursor

Offset

Stable infinite scroll

14. The final interview answer

#

"The feed shows posts from accounts a user follows, ranked, with p99 under 300 ms and new posts visible to followers within about ten seconds. For 10 million daily users that's about 3,500 feed reads a second and 70 posts a second at peak, but posts fan out to an average of 200 followers, and some celebrities have millions.

I use a hybrid model. When a normal author posts, the post service writes the post and an outbox event; fan-out workers insert the post id into each active follower's feed, a capped sorted set of ids in a sharded Redis cluster. Celebrity posts are not pushed; their recent posts are cached once and merged at read time. Inactive users get their feed rebuilt on demand.

A feed read takes the pushed ids, merges celebrity posts, ranks a few hundred candidates with a ranking service that falls back to chronological order, and hydrates the page from post, user and count caches, filtering deleted posts and anything the viewer isn't allowed to see. Authors see their own posts immediately; everything else is eventually consistent. The design scales by sharding feeds by user, scaling fan-out workers on stream lag, and tuning the celebrity threshold by measured cost."

Exercises

#

Exercise 1

Fan-out arithmetic

#

In one day, 1 million normal authors each post once, with an average of 300 active followers; 2,000 celebrities each post 3 times, with an average of 5 million followers. Compute the daily feed insertions under pure push, and under the hybrid model. Then estimate the extra read-time work the hybrid adds if the average user follows 4 celebrities and reads the feed 12 times a day (100 million DAU).

Exercise 2: Unfollow

#

A user unfollows an account. Explain what should happen immediately, what can happen later, and why you would not delete every item from that account in the user's feed synchronously.

Exercise 3: Ranking outage

#

The ranking service's p99 latency rises from 40 ms to 900 ms. What happens to feed latency in this design, and what should the feed service do automatically?

Exercise 4: Size the feed store

#

At social-media scale (500 million active users), you decide to keep only 300 ids per user in memory, each entry costing 16 bytes in practice (id plus score), with a structure overhead of 1.5 times. How much memory is needed, and how many 64 GB nodes (plus one replica each)?

Show solutionHide solution

Pure push:

Code
Normal: 1,000,000 posts x 300 = 300 millionCelebrities: 2,000 x 3 posts x 5,000,000 = 30 billionTotal: about 30.3 billion insertions/day (about 350,000/s on average, bursty)

The celebrities create 99% of the work.

Hybrid: only normal authors are pushed: 300 million insertions/day (about 3,500/s on average), a hundredfold reduction.

Extra read-time work in the hybrid: each feed read fetches recent posts for the 4 celebrities the user follows from the celebrity timeline cache: 100 million DAU x 12 reads x 4 = 4.8 billion cache reads/day, about 56,000/s on average. But they are reads of a small, hot set (2,000 celebrities' timelines), served from cache (and in-process caches), which is far cheaper than 30 billion writes.

Exercise 2

Show solutionHide solution

Immediately: remove the follow relationship (so no new posts from that account are pushed), and make the feed read path filter items whose author is no longer followed (hydration can check the follow relationship for the handful of authors on the page, or keep a small "recently unfollowed" set per user for a few days).

Later: an asynchronous job removes that author's post ids from the user's feed store, or simply lets them age out as new items are added and the feed is trimmed.

Why not synchronously: the feed may contain many items from that author, removing them takes multiple operations on the feed store, and doing it inline would make unfollow slow and fragile. Read-time filtering gives the correct experience immediately at negligible cost, and cleanup can be lazy.

Exercise 3

Show solutionHide solution

Without protection, every feed request waits up to 900 ms for ranking (or for its own timeout), so feed p99 jumps far above 300 ms, threads fill up (Little's Law), and the feed service may cascade into failure (article 23).

Automatic behaviour: the ranking call has a timeout of about 50 ms; on timeout, the feed uses chronological order for that request. A circuit breaker opens when timeouts exceed a threshold, so subsequent requests skip ranking entirely for a while and serve chronological feeds instantly; half-open probes check for recovery. A bulkhead caps concurrent ranking calls. The ranking fallback rate is alerted on, and users see a slightly less relevant but fast feed.

Exercise 4

Show solutionHide solution
Code
Raw: 500 million x 300 x 16 bytes = 2.4 x 10^12 bytes = 2.4 TBWith 1.5x overhead: 3.6 TBNodes: 3.6 TB / 64 GB = about 57 primaries; with one replica each, about 114 nodes

Add headroom (growth, uneven distribution, memory for operations), so perhaps 64 to 70 primaries. This is a significant fleet; alternatives worth discussing are keeping fewer ids in memory (for example 200, with older pages served by fan-out on read), keeping feeds only for users active in the last 7 days, or storing feeds in a disk-backed wide-column store with a cache for the first pages.

Where to go next

#
  • Next: Design Search Autocomplete (Typeahead), another read path that depends on precomputation.
  • Related: the event-driven read models of article 27 and the caching patterns of article 09 underpin this design.

Sources & further reading

  1. redis.io
  2. learn.microsoft.com

Linked from 1 guide

Other pages in the library that point here.

  • Guide · Technology

    Start Here: Your Roadmap from Zero to Job-Ready DSA

  • Guide · Technology

    Heaps and Priority Queues

  • Guide · Career

    A LinkedIn Profile That Gets Recruiter Messages