Chapter 11

Design a
News Feed System

A walk through how a social platform builds, stores, and serves a personalized stream of posts from the accounts each user follows — and the tradeoffs that keep it fast at scale.

System Design Series
Arrow keys or space to navigate · F for fullscreen
02 / 13
Scope

What the system must do

Before sketching boxes, pin down the small set of operations the feed actually has to support and the budgets it has to meet.

01

Publish a post

A user creates a text, image, or video post. The post becomes visible to everyone who follows that user.

02

Follow / unfollow

Users build their own audience graph. The follow edge drives which posts appear in whose feed.

03

View the feed

Open the app and see a ranked, paginated list of recent posts from followed accounts within a few hundred milliseconds.

04

Scale

Hundreds of millions of users, average tens to hundreds of follows each, with a long tail of accounts that have millions of followers.

05

Latency budget

Feed load should feel instant on mobile. Aim for p99 well under one second end to end, including network.

06

Freshness

New posts from people you follow should show up in seconds, not minutes — without a manual refresh in most cases.

03 / 13
Mental model

Every feed system has two halves

The hard part is not storing posts — it is deciding when to assemble a user's feed. That decision splits the system into a write path and a read path.

Write path

Feed publishing

What happens at the moment someone hits "post." The new entry has to be persisted, indexed, and somehow made discoverable by every follower's future feed read.

  • Validate and store the post
  • Decide whose feeds it should appear in
  • Optionally pre-compute those feeds now
Read path

Feed retrieval

What happens when a user opens the app. The system has to return a ranked page of posts quickly, drawing on whatever was pre-computed and whatever still has to be assembled on demand.

  • Look up the viewer's pre-built feed, if any
  • Merge in anything missing
  • Rank, paginate, and return
04 / 13
Strategy A

Fanout on write — the push model

When a post is created, the system actively pushes a reference to that post into the inbox of every follower. By the time anyone opens the app, their feed is already assembled.

Author posts content Post service persists post Fanout worker looks up followers Follower 1 · feed cache [postId, postId, postId, ...] Follower 2 · feed cache [postId, postId, postId, ...] Follower 3 · feed cache [postId, postId, postId, ...] Follower 4 · feed cache [postId, postId, postId, ...] Follower N · feed cache [postId, postId, postId, ...] One write triggers N inserts. Reads later are a single cache lookup.
05 / 13
Strategy B

Fanout on read — the pull model

Nothing happens to other users' feeds when a post is published. Instead, when a viewer opens the app, the system pulls the latest posts from each followed account and merges them on the fly.

Viewer opens feed News feed service resolves followed list Followed A · posts recent N entries Followed B · posts Followed C · posts Followed D · posts Followed E · posts Merge + rank k-way sort by time/score Reads do all the work. No state was prepared in advance.
06 / 13
Tradeoffs

Push and pull pay different costs

Neither model is universally better. The right answer depends on the shape of the social graph and the read-to-write ratio.

Dimension Fanout on write (push) Fanout on read (pull)
Read latency Fast — feed is already assembled, single lookup Slow — must query and merge many sources per request
Write cost High — one post triggers writes to every follower's inbox Low — one post is one insert, nothing more
High-follower accounts Painful — a post by a celebrity creates millions of fanout writes Cheap — followers all read from the same source list
Inactive followers Wasteful — feeds are built for users who may never log in Efficient — no work is done until someone actually asks
Storage overhead High — every user keeps a personalized cached list Low — only the underlying posts and follow graph
Best when Reads vastly outnumber writes; most accounts have modest follower counts Hot accounts have enormous followings; most users read rarely
07 / 13
Strategy C

Hybrid — push for the many, pull for the famous

Real systems mix both. Ordinary accounts use fanout on write, since their follower lists are small. A handful of high-fanout accounts are treated specially and pulled at read time.

Normal few hundred followers Fanout worker push to inboxes Celeb millions of followers Skip fanout store post only Viewer's inbox cache posts from normal follows Celebrity post store pulled on demand Merge at read rank, paginate Feed Most posts are pre-distributed. A small number of viral accounts are joined in only when needed.
08 / 13
Architecture

The pieces that make this work

A handful of focused services, each with a clear job. They communicate through queues and caches so the slow paths never block the fast ones.

Post service

Accepts new posts, validates them, stores them in the primary post database, and emits an event for downstream consumers. This service does not care about followers.

Fanout service

Subscribes to post events. For each new post, it loads the author's follower list and decides whether to push the post ID into each follower's feed cache or defer the work.

Feed cache

A per-user list of recent post IDs, kept in a fast key-value store. This is the structure the read path hits first. Sized to roughly the visible window of the feed.

News feed service

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

09 / 13
Data shape

What the feed cache actually stores

Each user gets a bounded, ordered list of post IDs — not the posts themselves. The post bodies live elsewhere and are hydrated on demand.

KEY feed:user:42 feed:user:91 feed:user:115 feed:user:208 ORDERED LIST OF POST IDS (NEWEST FIRST, CAPPED AT N) p_8841 p_8814 p_8790 p_8771 p_8742 p_8710 ··· p_8839 p_8803 p_8765 p_8744 p_8720 ··· p_8836 p_8792 p_8745 p_8701 ··· p_8840 p_8800 p_8770 ··· cap at N entries (e.g., 1000) older entries fall off the tail Bodies, authors, attachments — all hydrated separately. The cache only carries IDs.
10 / 13
Read API

Paginate with a cursor, not an offset

Feeds are constantly being prepended to. Offset-based pagination breaks the moment new posts arrive — items shift and pages start repeating or skipping. A cursor pins the read to a stable position in the stream.

Offset-based — fragile

Client asks for "page 2, 20 per page." If three new posts arrived between page 1 and page 2, the first three items of page 2 are the same items the client already saw at the end of page 1.

Request
GET /feed?offset=20&limit=20
  • Duplicates on prepend
  • Gaps if items are removed
  • Offset arithmetic is server-side work

Cursor-based — stable

Each page response carries an opaque cursor pointing just past the last returned item. The next request asks for "items older than this cursor." New posts prepended at the top do not disturb the cursor.

Request
GET /feed?cursor=eyJ0IjoxNzM...
  • No duplicates, no skips
  • Cursor can encode rank score, not just time
  • Naturally supports infinite scroll
11 / 13
Ordering

Chronological versus ranked

Once the candidate set of posts is assembled, the system has to decide what order to show them in. The choice shapes user behavior more than almost any other design decision.

Reverse chronological

Newest first, oldest last. The merge is a simple sort by timestamp.

  • Predictable — users understand the order
  • Cheap — no model serving on the read path
  • Easy to debug and reason about
  • Loses signal when followed accounts post at very different rates

ML-ranked

A scoring model assigns each candidate post a relevance score from features like recency, author affinity, predicted engagement, content type, and viewer history.

  • Surfaces posts the viewer is likely to engage with
  • Smooths over uneven posting frequency
  • Requires a feature pipeline and a low-latency model server
  • Harder to explain, easier to game, needs guardrails
12 / 13
Reality

The cases that break naive designs

A feed system is mostly correct on the happy path. Production complexity lives in the messy interactions between deletes, privacy, and the social graph.

DELETES

Post removed after fanout

The post ID may still sit in millions of feed caches. Either tombstone the post and filter on hydration, or rely on the hydration step to drop missing IDs gracefully.

BLOCKS

One user blocks another

Past posts from the blocked user must disappear from the viewer's feed. Filter at read time — never trust pre-built feeds to reflect the latest relationship state.

PRIVACY

Audience changes after posting

A user makes their account private, or restricts a post to a subset of followers. The feed pipeline must re-check visibility per viewer, not just at fanout time.

REPOSTS

Shares and quote reposts

A repost references another post. Both the repost and the original should not appear twice. Dedupe by underlying content, and decide whose voice anchors the feed entry.

UNFOLLOW

Existing entries from a now-unfollowed account

Already-cached post IDs from that author should fade out. Either purge on unfollow, or filter at read time against the current follow set.

RECONCILE

Cache drift

Caches can be lost, partially written, or fall out of sync with the post store. A periodic rebuild job restores feeds for active users from the source of truth.

13 / 13
Takeaways

Six principles to carry forward

A news feed is a long-running optimization problem balancing freshness, cost, and latency across a graph with a brutal long tail.

Split write and read paths

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

Mix push and pull

Push by default. Pull for accounts whose fanout would otherwise melt the write path.

Cache IDs, hydrate late

Keep the hot per-user list small. Resolve post 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, not the write-side snapshot.

Plan for repair

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

01 / 13