Design a social news feed: fan-out on write versus read, the celebrity problem, hybrid delivery, ranking basics, timeline storage, pagination and failure modes.
What you'll learn: By the end of this lesson you will be able to (1) compare fan-out on write, fan-out on read and hybrid feed designs with numbers, (2) explain and solve the celebrity problem, (3) design timeline storage, cursor pagination and caching, and (4) describe how a feed degrades when parts of it fail.
Imagine a newspaper stand. One option is to print a custom newspaper for every reader every time any journalist files a story. Reading is instant, but printing is enormous work. The other option is to keep stories in one pile and assemble each reader's paper only when they walk up. Printing is cheap, but every reader waits while you collect and sort.
A news feed faces exactly this choice. When someone you follow posts, do we do the work when they post (and pre-build your feed), or when you open the app (and build it on demand)? The best answer is usually a mix, chosen per author.
We assume you know caching, sharding and queues from the Building Blocks course. The post content itself (photos, captions) is covered in the photo sharing case study. Here we focus on how posts reach the right feeds.
The core idea
A feed is a materialised view of posts from the accounts you follow, ordered by time or relevance. You can compute it at write time, at read time, or both. Which one wins depends on the ratio of reads to writes and on the fan-out: the number of followers each post must reach. Because follower counts are wildly uneven, no single strategy fits every author.
Requirements and scope
Functional: publish a post, follow and unfollow, view a home feed of posts from followed accounts (newest first or ranked), scroll through older posts, see a new-post indicator.
Out of scope: post creation internals and media, comments and likes (we touch their counters briefly), notifications (see the notification service design), ads.
Non-functional (assumptions):
Feed load p99 under about 300 ms.
Posts appear in followers' feeds within a few seconds. Perfect real-time is not required.
High availability for reading. A slightly stale feed beats an error page.
Eventual consistency is acceptable, but a user should see their own post immediately (read-your-writes).
Back-of-envelope estimate
All numbers are illustrative assumptions.
Quantity
Assumption
Result
Daily active users
100 million
Posts per user per day
0.5 (most people read more than write)
50 million posts per day, about 580 per second average
Feed opens per user per day
10
1 billion feed loads per day, about 11,600 per second average
Peak factor
3x
about 35,000 feed loads per second
Average follows per user
200
Fan-out on write per post
follower count, average 200
about 10 billion feed insertions per day, about 115,000 per second
Feed page size
20 posts
The takeaway: reads are about 20 times more frequent than posts, but each post is written into hundreds of feeds. Pure fan-out on write converts 580 posts per second into about 115,000 timeline writes per second on average. That is large but spreadable. The danger is the tail. One account with 50 million followers turns one post into 50 million writes.
Storage per timeline entry: store only IDs and a sort key, say 16 to 24 bytes per entry. Keeping the latest 800 entries per active user: 100 million users times 800 times about 20 bytes is about 1.6 TB for timelines. That fits in a sharded in-memory or SSD-backed store. Post bodies are stored once, elsewhere.
API design
POST /v1/posts -> 201 { "postId": "..." }
POST /v1/follows/{userId} -> 204
DELETE /v1/follows/{userId} -> 204
GET /v1/feed?cursor=<opaque>&limit=20 -> 200 { "items": [...], "nextCursor": "..." }
GET /v1/feed/new-count?since=<cursor> -> 200 { "count": 7 }
The cursor is opaque to clients. Internally it encodes the sort key of the last item seen (for example a post ID that encodes time). We will see why this matters in the pagination section.
Data model
Post store:posts(post_id, author_id, body_ref, created_at, ...), keyed by post_id. A wide-column or sharded relational store both work. Post IDs should be time-sortable (see IDs, clocks and ordering) so that sorting IDs sorts by time.
Social graph: two mappings: following(user_id -> followee ids) and followers(user_id -> follower ids). Both are needed: reading a feed on demand needs "who do I follow", and fan-out needs "who follows this author". Store both, sharded by user_id. For huge follower lists, store them as paged or chunked rows.
Timeline store:timeline(user_id, post_id) sorted by post_id descending, capped to the latest N entries. A sorted set in an in-memory store (Redis sorted sets are a common choice) or a wide-column table with clustering by time fits. Shard by user_id so one feed read hits one shard.
Counters (likes, comments): kept separately and updated asynchronously. Do not store counts inside the timeline.
How to read it: TIMELINE_ENTRIES is a derived table of pairs and can be rebuilt from POSTS and FOLLOWS. The is_celebrity flag is the switch that selects push or pull for an author.
High-level design
Reading a feed: the feed service fetches post IDs from the timeline store for this user, merges in recent posts from any celebrity accounts they follow, hydrates the IDs into full posts from a post cache (batched), optionally re-ranks, and returns a page. To give the author read-your-writes, the author's own latest posts are merged in the same way (or written into their own timeline synchronously at publish time), so they do not wait for the asynchronous fan-out.
Reference deployment topology
In deployment terms the design splits into a synchronous read path, an asynchronous fan-out path and a sharded data tier, spread over at least two zones.
How to read it: a post takes the path post service, Kafka topic, fan-out workers, timeline shards (or the celebrity index for a high-fan-out author), while a feed read goes from the feed service to the timeline shards, the celebrity index and the post cache. Queue lag is the main thing that makes the two paths drift apart.
Deep dive 1: fan-out on write, on read, and hybrid
Fan-out on write (push). When a post is created, workers look up the author's followers and insert the post ID into each follower's timeline. Reading is a single key lookup: cheap and fast. The cost is write amplification, wasted work for followers who never log in, and slow propagation for very large authors.
Fan-out on read (pull). Store only posts. On a feed load, fetch the list of accounts the user follows, query each one's recent posts, and merge. Writes are trivial and never wasted. Reads are expensive: with 200 follows, one feed load is up to 200 lookups plus a merge, on the critical path.
Hybrid. Push for ordinary authors, pull for celebrities. The rule is a follower-count threshold (for illustration, something like 100,000 followers, tuned from measurements). Posts from accounts below the threshold are pushed into followers' timelines. Posts from accounts above it are not fanned out; they are kept in a per-author recent-posts list. At read time the feed service merges the user's precomputed timeline with the latest posts of the few high-fan-out accounts the user follows. Since most people follow only a handful of celebrities, the extra read work is small.
sequenceDiagram
participant A as Author
participant PS as Post service
participant Q as Queue
participant W as Worker
participant T as Timeline store
participant R as Reader
participant F as Feed service
A->>PS: publish post
PS->>Q: new post event
Q->>W: deliver event
W->>T: push to followers if author is not a celebrity
R->>F: open feed
F->>T: read timeline
F->>F: merge celebrity posts and rank
F-->>R: first page
Strategy
Use when
Cost
Alternative
Fan-out on write
Read-heavy, modest and even follower counts
Write amplification, wasted work for inactive users, slow for huge authors
Hybrid
Fan-out on read
Few follows per user, strong freshness, small scale
Slow, expensive reads on the critical path
Cache merged results briefly
Hybrid
Skewed follower distribution (typical social networks)
Two code paths, threshold tuning, merge logic
Push only to active users
Further optimisations. Fan out only to recently active followers and rebuild a dormant user's timeline by pulling on return. Batch timeline writes and partition the queue by follower shard so a big fan-out spreads over many workers. Throttle one author's fan-out so a spike does not starve other work.
NOTE
The threshold is a policy, not a law. Measure the cost of pushing (writes per second, latency to last follower) and the cost of pulling (extra reads per feed load) and choose the point where they balance for your traffic.
The per-author decision can be written as a flow that the fan-out worker runs for every new post.
flowchart TD
P["New post event"] --> A{"Followers above threshold?"}
A -->|yes| B["Write to author recent-posts list only"]
A -->|no| C{"Is the follower active recently?"}
C -->|yes| D["Push post ID to their timeline"]
C -->|no| E["Skip, rebuild by pull on return"]
B --> F["Readers merge it at read time"]
How to read it: only the above-threshold branch (author recent-posts list) adds read-time work, and only the push branch costs write-time work. The threshold and the activity window are the two knobs.
Deep dive 2: ranking, storage and pagination
Ranking basics. Reverse-chronological is simplest and predictable. Most products rank. A common shape is a two-stage pipeline: candidate generation (the timeline plus celebrity posts, perhaps a few hundred candidates) followed by scoring (a model or a hand-tuned formula using signals such as recency, author affinity, past engagement and post type), then a final pass for diversity (do not show ten posts from one author in a row) and policy rules. Scoring happens at read time over a bounded candidate set, so cost stays predictable. We will not design the model here; the system-design point is that the ranking service must be fast, have a timeout and a fallback to plain chronological order if it is slow or down.
Why cursors beat offsets. With OFFSET 40 LIMIT 20, new posts arriving at the top shift everything, so the user sees duplicates or skips items. A cursor says "give me items older than this ID", which is stable under insertions and efficient on a sorted index. For a ranked feed, which is not strictly ordered by time, the server may snapshot the ranked list of candidate IDs for a short time (a "feed session"), store it in a cache keyed by a session ID, and page through the snapshot. The cursor then encodes session and position.
Timeline storage details. Cap each timeline (for example the latest few hundred to a thousand entries). Older scrolling falls back to a slower pull path over the post store. Capping bounds memory and makes pushes cheap because you trim on insert. Keep entries small: IDs, not bodies. Hydrate bodies through a cache because a hot post appears in millions of timelines, and you want one cached copy, not millions.
Unfollow, delete and block. A delete must disappear from feeds. Rather than hunting through millions of timelines, the feed service filters at read time: after hydration, drop posts that are deleted, from blocked authors, or no longer visible to this viewer. Lazy cleanup removes stale IDs in the background. Unfollow works the same way: filter by current following list at read time.
Caching strategy
Layers, from the outside in: the client cache keeps the last page so opening is instant; the timeline store is already a precomputed result for active users; the post cache holds post bodies and author snippets cache-aside, with TTLs and explicit invalidation on edit or delete; an optional feed page cache holds the rendered page for a few seconds, but personalisation keeps hit ratios modest, so evaluate before adding it.
Hot keys are a real risk: a viral post's cache entry may receive enormous read traffic. Replicate hot entries across cache nodes or keep a tiny in-process cache in each feed server with a very short TTL. See advanced caching for hot-key and stampede techniques.
Bottlenecks and scaling
Fan-out workers. Scale on queue lag. Partition work by follower shard so each worker batch writes to one timeline shard.
Timeline store. Shard by user_id. Memory is the dominant cost, so cap lengths and consider keeping only active users in memory.
Social graph reads. Fan-out needs the follower list, which can be millions of IDs. Page through it, and cache the lists of mid-sized authors.
Feed service latency. The slowest dependency sets the tail. Issue timeline, celebrity and cache reads in parallel and set per-call timeouts.
Ranking cost. Bound candidate set size and score in batches. Keep a cheap fallback.
Failure modes
Failure
Effect
Mitigation
Fan-out queue backlog
New posts delayed in feeds
Autoscale on lag, prioritise small authors, shed or throttle huge fan-outs, alert on lag
Timeline store node lost
Some users have empty or stale feeds
Replicas, and a rebuild path: pull recent posts from followed accounts and refill
Ranking service slow or down
Slow feeds
Timeout and fall back to chronological order
Post cache stampede
Database overload
Request coalescing, jittered TTLs
Duplicate fan-out (retry)
Same post twice in a timeline
Idempotent insert keyed by (user_id, post_id), so repeats are no-ops
Celebrity post storm
Hot cache keys, read spike
Replicate hot entries, per-process micro-cache
Graph store unavailable
Cannot fan out or filter
Retry from queue, serve existing timelines without new entries
Deleted post still shown
Trust and legal problem
Read-time visibility filter, plus lazy cleanup
The rebuild path deserves emphasis. A cache or timeline store is a derived copy, so you must be able to regenerate it. If you cannot rebuild timelines from the post store and graph, you have turned a cache into your database without realising it.
Trade-offs worth stating
Decision
Option A
Option B
How to choose
Feed order
Chronological
Ranked
Chronological is simple and transparent. Ranked boosts engagement but needs a model and a fallback
Fan-out target
All followers
Active followers only
Active only saves large amounts of work, at the cost of a rebuild on return
Timeline content
IDs only
Full post copies
IDs only. Copies make edits and deletes painful
Freshness
Strong, immediate
Eventual, seconds
Seconds of delay are rarely noticed and cut cost sharply
Celebrity threshold
Low
High
Tune from measured push cost versus pull cost
Interview cheat sheet
Step
What to say
Key numbers (illustrative)
Requirements
Publish, follow, home feed, scroll. Read-heavy, seconds of delay OK
100M DAU
Estimate
Pure push means billions of timeline writes per day
580 posts/s, about 115k timeline writes/s, 35k feed loads/s peak
API
Opaque cursor pagination
20 per page
Data
Posts, two graph mappings, capped timeline of IDs
About 1.6 TB timelines
Design
Queue and fan-out workers, timeline store, feed service, post cache
Hybrid push and pull
Deep dives
Hybrid threshold and celebrity merge, ranking pipeline with fallback, cursor stability
Candidate then score
Failures
Fan-out lag, timeline loss and rebuild, stale deletes
Idempotent inserts
Trade-offs
Push versus pull, ranked versus chronological, active-only fan-out
Threshold is tunable
In practice: operating the reference design
NOTE
A realistic scenario, not a case history. This is a composite example built from common production patterns. It does not describe any specific company, and every number in it is an illustrative assumption.
Assume a platform team of ten runs this on Kubernetes with Kafka for the fan-out topic, Redis for timelines and the post cache, PostgreSQL or a wide-column store for posts, and Prometheus and Grafana for monitoring.
SLOs and dashboards
SLI
SLO (assumed)
Dashboard panels
Feed load succeeds
99.9% over 30 days
Success ratio, p50 and p99 latency per dependency
Feed load latency
p99 under 300 ms
Timeline read, celebrity merge, hydration and ranking time
Fan-out freshness
99% of pushes land within 10 s
Consumer lag in messages and seconds, worker utilisation
Ranking fallback rate
Under 1% of loads
Ranking timeouts, fallback count
Deleted content hidden
Within 60 s
Post cache invalidation age, read-time filter hits
Capacity-review cadence
Weekly: fan-out lag at peak, timeline memory headroom per shard, post cache hit ratio, the top 20 authors by follower count.
Monthly: re-measure the celebrity threshold from real push cost versus pull cost, and re-check follower-count distribution drift.
Quarterly: replay a peak day against a staging cluster, and run a timeline-shard rebuild drill.
Runbook
Symptom
Likely cause
First check
Mitigation
New posts show up late
Fan-out lag after a large author posts
Consumer lag by partition, per-author push rate
Throttle that author, add workers, raise their celebrity flag
A slice of users sees empty feeds
Timeline shard lost or evicted
Shard health, key count per shard
Fail over to replica, rebuild most active users first
Feed latency up, errors flat
Ranking or hydration slow
Per-dependency timings
Shorten ranking timeout, serve chronological
Database load spikes
Post cache stampede or hot key
Cache hit ratio, top keys
Coalesce requests, jitter TTLs, replicate the hot entry
Same post twice
Retry without idempotent insert
Insert path logs
Use set semantics keyed by user and post
Deleted post still visible
Cache not invalidated
Post cache entry for that ID
Invalidate explicitly, verify the read-time filter
Decision record: hybrid fan-out threshold
Field
Entry
Context
Pure push turns one post from a 50-million-follower author into 50 million writes. Pure pull makes every feed open do up to 200 lookups.
Decision
Push for authors under a follower threshold (start at 100,000, tune monthly), pull for the rest, and push only to followers active in an assumed 30-day window.
Consequences (good)
Bounded write amplification, cheap reads for most users, no storm from one post.
Consequences (bad)
Two read paths, a merge step with its own latency, and a rebuild path for returning users.
Revisit when
Merge time exceeds a set share of the 300 ms budget, or the number of authors above the threshold grows enough that pull cost dominates.
Cost view
Memory for timelines dominates. At the lesson's assumptions the capped timelines are about 1.6 TB, so roughly 3.2 TB with one replica per shard, before overhead. Fan-out compute and post-cache memory follow.
Cost driver
Lever
Timeline memory
Cap length, store IDs only, keep dormant users out of memory
Fan-out compute
Active-only push, batching per shard, a celebrity threshold
Post cache
Short TTLs for volatile fields, one cached copy per post
Ranking
Bound candidate count, score in batches
Architect's notes
Measure the follower distribution before picking a threshold. The shape of the tail, not the average, decides the design.
Ask what "fresh" means to the product. If seconds of delay are fine, you can batch and throttle aggressively. If not, the budget changes everywhere.
Watch memory, not just CPU. At these assumptions timeline memory (see the cost view) is the largest line item, so size it as carefully as the workers.
Decide who owns the ranking fallback. The ranking team must agree that chronological order is an acceptable degraded mode, and who gets paged when it activates.
Defer personalisation depth. Ship chronological plus a simple score before building models. Keep the fallback working at every step.
Rehearse the rebuild. A timeline store you cannot rebuild is a database nobody backed up.
Common mistakes
Choosing pure fan-out on write. It breaks on accounts with huge follower counts. Use a hybrid with a threshold.
Choosing pure fan-out on read for a large product. Feed loads become the slowest, most expensive path. Precompute for ordinary authors.
Storing full post bodies in every timeline. Edits and deletes become impossible to apply. Store IDs and hydrate through a cache.
Using offset pagination. New items cause duplicates and skips, and deep offsets are slow. Use cursors.
Ignoring deletes, blocks and unfollows. Stale entries leak content. Filter at read time and clean up lazily.
No fallback for ranking. A ranking outage becomes a feed outage. Time out and fall back to chronological.
Treating the timeline store as the source of truth. Without a rebuild path, one lost node loses data. Keep it derivable.
Key takeaways
A feed is a materialised view. The design question is when to compute it: at write time, read time, or both.
Fan-out is the cost driver. Posts times followers is the number that matters, and it is skewed.
Hybrid wins for skewed graphs. Push for ordinary authors, pull for celebrities, merge at read time.
Store IDs, hydrate from cache. One cached copy of a post serves millions of timelines.
Cursors keep scrolling stable. Offsets break when the top of the list changes.
Rank in two stages. Generate bounded candidates, then score, with a chronological fallback.
Everything derived must be rebuildable. Plan the rebuild path for timelines and caches.
Filter deletions at read time. Cleanup is lazy, visibility is immediate.
Practice exercises
Exercise 1 — Starter (30 min): Using the illustrative numbers in this lesson, compute the timeline writes per second if every post fanned out to every follower, for authors averaging 200 followers versus 2,000 followers. Then compute how long one post from a 50-million-follower author would take to fan out if workers collectively sustain 200,000 writes per second.
Exercise 2 — Intermediate (2 hours): Design the data schema and the exact read algorithm for the hybrid feed: how the feed service merges the timeline with three followed celebrities' recent posts, deduplicates, handles a user who just followed a celebrity, and produces a cursor that stays valid across pages. Write pseudocode and an example with five timeline entries and two celebrity posts.
Exercise 3 — Advanced (half day): Add "mute this author" and "post edited" features. Specify what changes in the timeline store, the post cache, the read-time filters and the ranking inputs. Then write a runbook for the failure where the timeline store loses a shard: how you detect it, how you rebuild affected users (most active first), and how the product behaves in the meantime.
Lesson 11 of 14 · Advanced System Design and Case Studies
Design a social news feed: fan-out on write versus read, the celebrity problem, hybrid delivery, ranking basics, timeline storage, pagination and failure modes.
In the rest of this lesson
Data model
High-level design
Reference deployment topology
Deep dive 1: fan-out on write, on read, and hybrid
Deep dive 2: ranking, storage and pagination
Caching strategy
Enrolling is free. It unlocks every lesson in Advanced System Design and Case Studies, tracks your progress across devices and keeps your quiz scores. The newsletter is optional.
Real architecture decisions, LLMOps patterns that survive production, and engineering leadership advice — from 12+ years of building at enterprise scale. Free. No spam. Unsubscribe anytime.
Get new articles by email
Free. We email a confirmation link first, and you can unsubscribe from any email.