Chapter 11 · System Design Fundamentals

Design a News Feed System

A feed looks like a simple list, but underneath it is a merge: for every viewer, gather the recent posts of everyone they follow, order them, and return a page — fast, at the scale of hundreds of millions of users with a brutal celebrity long tail. The craft is in when you do that merge, not how.

Open the companion slides
Reading time ~12 min Prerequisites Ch 10 · Notification System, Ch 2 · Estimation Audio 🔊 Hinglish read-aloud Next Design a Chat System →

A news feed is a list, but building it is a merge problem in disguise. For each viewer you have to collect the recent posts from every account they follow, put them in order, and hand back one page — within a few hundred milliseconds, over a follow graph where most accounts are small but a handful have tens of millions of followers. Everything in this chapter turns on one question: do you assemble a viewer's feed at the moment a post is written, or at the moment the feed is read? Every strategy below is a different answer to that single question.

Primary source

Alex Xu, System Design Interview (Vol. 1), Chapter 11. Companion deck: slides — jump to any slide with the Slide N chips. Go deeper: the System Design Primer's social-network data-structures section.

What a feed system must do Slide 2

The functional surface is tiny — publish a post, follow accounts, read a feed — and that simplicity hides the hard part. The design is dominated by non-functional constraints: latency, the shape of the social graph, and a read-to-write ratio that is wildly lopsided. Pin these down before drawing any boxes.

Publish a post

A user creates text, an image, or a video. It must become visible to every one of their followers' future feed reads — the moment it becomes visible is a design choice, not a given.

Build the follow graph

Follow and unfollow edges decide whose posts land in whose feed. This directed graph is the input to every merge, and it changes constantly.

Read the feed

Open the app and get a ranked, paginated page of recent posts from followed accounts. Aim for p99 well under a second, network included — it has to feel instant on mobile.

Survive the long tail

Hundreds of millions of users, tens to hundreds of follows each — but some accounts have millions of followers. That one skew breaks any design that treats all authors the same.

Feeds are read-heavy: people scroll far more than they post, so reads outnumber writes by a hundred to one or worse. That ratio — worth an envelope estimate — is the single biggest hint about where to spend effort.

Every feed has two halves Slide 3

Split the system in two along the pivotal question. The write path runs when someone posts; the read path runs when someone opens the app. The merge work has to live in one of them — the entire tradeoff space is a negotiation over which half pays for it.

Write path · feed publishing

Triggered by "post." Validate and persist the post, then decide whose feeds it belongs in and whether to pre-compute those feeds now. Work done here makes later reads cheap.

Read path · feed retrieval

Triggered by opening the app. Look up whatever was pre-built, merge in anything still missing, then rank, paginate, and return. Work done here is paid on every single view.

Fanout on write — the push model Slide 4

Do the merge eagerly. The instant a post is created, a fanout worker looks up the author's followers and pushes the new post's ID into each one's personal feed cache — a per-user "inbox." By the time any follower opens the app, their feed is already assembled and a read is a single cache lookup. One write triggers N inserts; the read cost collapses to almost nothing.

Author posts Post service persist post Fanout worker load followers Follower 1 feed cache Follower 2 feed cache Follower 3 feed cache Follower 4 feed cache Follower N feed cache One write, N inserts — the read later is a single cache lookup.
Push moves the merge to the write path. The cost is paid once, up front, spread across every follower's inbox — so reads are trivially fast.

Fanout on read — the pull model Slide 5

Do the merge lazily. When a post is published, nothing happens to anyone else's feed — it is just one insert. The work waits until a viewer opens the app: the feed service resolves who they follow, queries each of those accounts' most recent posts, and merges the streams on the fly with a k-way sort by time or score. Writes are dirt cheap; reads do all the work.

Viewer opens feed Feed service resolve follows Author A · posts Author B · posts Author C · posts Author D · posts Author E · posts Merge + rank k-way by time Reads do all the work — nothing was prepared in advance.
Pull keeps the write path a single insert and pushes the merge to the read path. Dashed arrows are queries out; solid arrows are results merged in.

Push and pull pay different costs Slide 6

Neither model wins outright — they move the same cost to opposite ends. Push buys fast reads with expensive writes and lots of duplicated storage; pull buys cheap writes with slow, work-heavy reads. Which one hurts depends on the shape of your graph and your read-to-write ratio.

DimensionFanout on write (push)Fanout on read (pull)
Read latencyFast — feed pre-assembled, one lookupSlow — query and merge many sources per read
Write costHigh — one post fans out to every inboxLow — one post is one insert
Hot accountsPainful — a celebrity post is millions of writesCheap — followers all read one source list
Inactive followersWasteful — feeds built for users who never log inEfficient — no work until someone asks
StorageHigh — every user keeps a cached listLow — just posts and the follow graph
Best whenReads >> writes; most accounts are smallA few accounts are huge; most users read rarely
The celebrity problem

Pure push dies on the long tail. When an account with 40 million followers posts, fanout-on-write has to perform 40 million inserts for a single action — a write amplification that stalls the queue and delays everyone else's fanout. This one skew is why no large system uses push alone.

Hybrid — push for the many, pull for the famous Slide 7

Real systems refuse to choose. Ordinary accounts — the overwhelming majority, with modest follower lists — use push: their posts are pre-distributed into inboxes. A small set of high-fanout accounts above some follower threshold are exempted from fanout; their posts are just stored, and pulled at read time and merged into the viewer's pre-built inbox. Most of a feed arrives pre-computed; only the few viral authors are joined in on demand.

Normal user hundreds follow Fanout worker push now Inbox cache pre-built list Celebrity millions follow skip fanout · store only Celeb post store pulled at read Merge at read rank · paginate Feed Most posts arrive pre-distributed; a few viral accounts are joined in only when a feed is read.
Hybrid caps write amplification: the expensive fanout is skipped precisely for the accounts whose fanout would be ruinous, and the cost of joining them moves to a bounded read-time merge.

The pieces that make this work Slide 8

A handful of narrow services, each with one job, wired together through queues and caches so the slow paths never block the fast ones. The post service does not know about followers; the fanout service does not know about ranking. Keeping those boundaries clean is what lets each scale independently.

Post service

Accepts, validates, and stores new posts in the primary post database, then emits a "post created" event. It owns the source of truth for content — and nothing about who sees it.

Fanout service

Consumes post events off a queue. For each post it loads the author's followers and decides — per the hybrid rule — whether to push the post ID into each inbox or defer to a read-time pull.

Feed cache

A per-user list of recent post IDs in a fast key-value store. This is the first thing the read path touches, and it holds only IDs, sized to the visible window of the feed.

News feed service

Handles reads. Pulls the cached ID list, merges in pull-mode sources, hydrates each ID into a full post, applies ranking and pagination, and returns the page.

What the feed cache actually stores Slide 9

The cache does not hold posts — it holds a bounded, ordered list of post IDs, newest first, capped at some N (say the last thousand). The bodies, author info, and attachments live elsewhere and are hydrated only when a page is actually read. Storing IDs keeps each per-user entry tiny, which is what makes it affordable to keep a personalized list for hundreds of millions of users in memory.

KEY RECENT POST IDS · NEWEST FIRST · CAPPED AT N feed:user:42 p_9042 p_9037 p_9021 p_9008 p_8990 feed:user:91 p_9041 p_9033 p_9012 p_8999 feed:user:115 p_9040 p_9030 p_9019 p_9004 p_8988 cap N tail drops
Key → capped ID list. The cache is small on purpose; hydration resolves each ID to a full post later, against the latest privacy rules.

Because entries are just IDs, a quick estimate shows the whole cache fits comfortably in memory — the reason push is affordable at all. Hydration is a batched key lookup against the post store, often itself cached.

Paginate with a cursor, not an offset Slide 10

A feed is constantly being prepended to, which quietly breaks offset pagination. If new posts arrive between the client fetching page 1 and page 2, every item shifts down by that many slots — so page 2 repeats items the user already saw, or skips others. A cursor pins the read to a stable position in the stream instead of counting from the top.

Offset — fragile

The client asks for offset=20&limit=20. Three posts prepended since page 1 push the boundary down, so the first three items of page 2 are duplicates. Removals leave gaps. The server does offset arithmetic on every call.

Cursor — stable

Each page returns an opaque cursor pointing just past the last item; the next request asks for "items older than this cursor." Prepends at the head do not move it — no duplicates, no skips, and it can encode a rank score, not just time.

Rule of thumb

Anything mutable at the head of a list needs an order-stable pointer, not an integer offset. Cursor pagination is also what makes seamless infinite scroll work — the client just keeps handing back the last cursor it received.

Chronological versus ranked Slide 11

Once the candidate posts are gathered, the system decides their order — and that choice shapes user behaviour more than almost any other. Reverse-chronological is honest and cheap; ML-ranked surfaces more of what a viewer will engage with, at the cost of a whole prediction pipeline on the read path.

Reverse chronological

Newest first — the merge is a plain sort by timestamp. Predictable, cheap, and trivial to debug. But it loses signal when followed accounts post at wildly different rates: one prolific account can bury everyone else.

ML-ranked

A model scores each candidate from features like recency, author affinity, predicted engagement, and viewer history. It smooths uneven posting and lifts relevance — but needs a feature pipeline, a low-latency model server, and guardrails against gaming.

The cases that break naive designs Slide 12

A feed is easy on the happy path. Production complexity lives in the messy interaction between the social graph, privacy, and a post ID that may already sit in millions of caches. The common thread: the write-time snapshot goes stale, so the read path has to be the source of truth for who sees what, now.

CaseWhat breaksFix
DeleteThe post ID still sits in millions of inboxes after removal.Tombstone the post; drop missing/removed IDs during hydration.
BlockPast posts from a newly blocked user linger in the feed.Filter against the block list at read time, never trust the cache.
Privacy changeAn account goes private, or a post's audience narrows, after fanout.Re-check visibility per viewer on hydration, not just at post time.
RepostA share and its original both surface, showing the same content twice.Dedupe by underlying content; pick whose voice anchors the entry.
UnfollowCached IDs from a now-unfollowed author keep appearing.Purge on unfollow, or filter against the current follow set at read.
Cache driftFeed caches are lost, partially written, or fall out of sync.Run a background reconciliation job that rebuilds from the source of truth.

Principles to carry forward Slide 13

Split write and read paths

Decide where the merge happens. Every other choice flows from that one decision.

Mix push and pull

Push by default; pull for the few accounts whose fanout would melt the write path.

Cache IDs, hydrate late

Keep the per-user list small and resolve bodies on demand, against the latest privacy rules.

Paginate with cursors

Anything mutable at the head needs an order-stable pointer, not an integer offset.

Filter at read time

Blocks, privacy, and unfollows change faster than caches. Trust the read-side check.

Plan for repair

Caches drift. A background reconciliation job is part of the design, not an afterthought.

Active recall

Cover the answers. Say each one out loud before you tap to check.

What single question splits a feed system in two?
When, not how.
Do you assemble the feed at write time (push into inboxes) or at read time (merge on demand)? The write path and read path are the two halves.
Push vs pull — where does each put the cost?
Opposite ends.
Push = expensive writes (fan out to N inboxes) but a one-lookup read. Pull = one-insert writes but expensive reads (query and merge many sources).
Why does pure push break, and how does hybrid fix it?
Think celebrities.
A high-follower post triggers millions of fanout writes. Hybrid pushes for normal accounts and pulls for high-fanout accounts, merging them at read time.
What does the feed cache actually store?
Not the posts.
A bounded, ordered list of post IDs, newest first, capped at N. Bodies are hydrated separately at read time, so each user's entry stays tiny.
Why cursor pagination instead of offset?
The head keeps moving.
Feeds are prepended to, so offsets shift and pages duplicate or skip. A cursor pins a stable position — no dupes, no gaps, and it supports infinite scroll.
Where do you enforce blocks, privacy, and unfollows?
Not the write-time snapshot.
At read time, on hydration. The relationship state changes faster than caches, so the read-side check — not the pre-built feed — is the source of truth.

Check yourself

Q1 In fanout on write, what happens the moment a post is created?
Why: Push does the merge eagerly — the fanout worker inserts the post ID into each follower's inbox, so a later read is a single cache lookup.
Q2 Which model struggles most with an account that has millions of followers?
Why: Push amplifies one write into a write per follower. A celebrity post becomes millions of inserts — the write-amplification that pull avoids by reading one source list.
Q3 What does a per-user feed cache entry contain?
Why: The cache holds only IDs, newest first, capped at N. Bodies are hydrated separately — that is what keeps the cache small enough to keep for every user.
Q4 Why is cursor pagination preferred over offset pagination for a feed?
Why: Feeds grow at the head. An offset counts from the top, so a prepend moves every item; a cursor pins a stable position past the last item seen.
Q5 A user blocks someone after that person's posts were already fanned out. Where do you fix it?
Why: The pre-built feed is a stale snapshot. Relationship state changes faster than caches, so blocks, privacy, and unfollows are enforced on the read path during hydration.