Skip to main content

Design a News Feed System

Adapted from: Alex Xu, System Design Interview – An Insider's Guide (2020), Chapter 11 — rewritten and condensed for teaching.

A news feed is the constantly updating list of stories in the middle of your home page: status updates, photos, videos, links, app activity, and likes from the people, pages, and groups you follow. Facebook's feed, Instagram's feed, and Twitter's timeline are all versions of the same interview question.

Lesson 3 used this problem to walk through the interview framework. This lesson works through the full design, with the central trade-off: fanout on write vs. fanout on read.

What you'll be able to do after this lesson:

  • Design the publish and retrieve APIs and the two main flows.
  • Compare push (fanout on write) and pull (fanout on read), quantify the celebrity/hotkey problem, and justify a hybrid.
  • Build a fanout service that respects mutes, keeps the feed cache small by storing only IDs, and merges celebrity posts at read time.
  • Hydrate feed IDs into full posts, and lay out a five-layer cache architecture.

Step 1: Understand the Problem and Establish Scope​

Candidate asksInterviewer answers
Mobile, web, or both?Both
Important features?A user can publish a post and see friends' posts on their feed
Feed order: reverse chronological, or ranked (e.g. close friends first)?Reverse chronological, to keep it simple
Max friends per user?5,000
Traffic?10 million DAU
Media?Images and videos as well as text

Step 2: High-Level Design​

There are two flows:

  • Feed publishing: a user posts. The post is written to the cache and the database, then fanned out to friends' feeds.
  • News feed building: a user loads their feed, which is assembled from friends' posts in reverse-chronological order.

APIs​

Both are HTTP APIs, authenticated with an auth_token.

POST /v1/me/feed
content: text of the post
auth_token: authenticates the request

GET /v1/me/feed
auth_token: authenticates the request

Feed publishing (high level)​

flowchart LR
U["User<br/>POST /v1/me/feed?content=Hello"] --> LB[Load balancer] --> WS[Web servers]
WS --> PS[Post service] --> PC[(Post cache)] & PDB[(Post DB)]
WS --> FS[Fanout service] --> NFC[(News feed cache)]
WS --> NS[Notification service]
  • Load balancer distributes traffic across web servers.
  • Web servers route requests to internal services.
  • Post service persists the post in the database and cache.
  • Fanout service pushes the new post into friends' news feeds, which are kept in a cache for fast reads.
  • Notification service tells friends there's new content and sends push notifications (the system from Lesson 10).

News feed building (high level)​

flowchart LR
U["User<br/>GET /v1/me/feed"] --> LB[Load balancer] --> WS[Web servers] --> NFS[News feed service] --> NFC[(News feed cache)]
  • The news feed service reads the user's feed from the news feed cache, which holds the post IDs needed to render it.

Step 3: Design Deep Dive​

Feed publishing deep dive​

flowchart LR
U[User] --> LB[Load balancer] --> WS["Web servers<br/>auth + rate limit"]
WS --> PS[Post service] --> PC[(Post cache)] & PDB[(Post DB)]
WS --> FS[Fanout service]
FS -- "1. friend IDs" --> G[(Graph DB)]
FS -- "2. friend info + settings" --> UC[(User cache)] --> UDB[(User DB)]
FS -- "3. friend list + post ID" --> MQ[[Message queue]]
MQ --> FW[Fanout workers]
FW -- "4–5. store post_id → each friend's feed" --> NFC[(News feed cache)]
WS --> NS[Notification service]

Web servers​

Besides talking to clients, web servers enforce:

  • Authentication: only signed-in users with a valid auth_token can post.
  • Rate limiting: cap posts per user per period to prevent spam and abuse (see Lesson 4).

Fanout service​

Fanout is the process of delivering a post to all of the author's friends. There are two models.

Fanout on write (push model). The feed is precomputed when the post is written. As soon as a post is published, it's delivered to every friend's feed cache.

ProsCons
Feeds are generated in real time and pushed to friends immediatelyAn author with many friends or followers makes publishing slow: fetching them all and writing every feed. This is the hotkey problem
Reading is fast, because the feed is already builtWasted work for inactive users who rarely log in

Fanout on read (pull model). The feed is built on demand when the user reads it. Recent posts are pulled when the user opens the home page.

ProsCons
No wasted work for inactive usersReading is slow, because the feed isn't precomputed
No hotkey problem, since nothing is pushed

Hybrid (our choice). Fast feed reads matter most, so use push for most users. For celebrities, users with huge follower counts, let followers pull their posts at read time, so one post doesn't trigger millions of writes. Consistent hashing (Lesson 5) further spreads hot keys and requests across servers.

Practice: how bad is the hotkey problem?​

Implement fanout_cost(authors, threshold). Each author is a (followers, posts_per_day) pair. Return a tuple:

  • push_writes: feed-cache writes per day if every author uses push (followers × posts).
  • hybrid_writes: writes per day if only authors with followers <= threshold push.
  • pulled_posts: posts per day from authors above the threshold. These are left for readers to pull.

Input: line 1 is threshold, and each following line is followers posts_per_day.

In the first test, one celebrity posting 3 times a day accounts for 90 million of the ~90 million writes. Under the hybrid model the system does just 5,400 writes, and followers pull 3 posts at read time.

How the fanout service works​

  1. Fetch friend IDs from the graph database. Graph databases suit friend relationships and friend recommendations.
  2. Get friend info from the user cache and filter by settings. If you've muted someone, their posts stay out of your feed even though you're still friends. Authors can also share a post with only some friends.
  3. Send the friend list and the new post ID to a message queue.
  4. Fanout workers pull from the queue and write to the news feed cache.
  5. The cache is essentially a <post_id, user_id> mapping: each new post is appended to each friend's list.

Store only IDs, and cap the list. Storing whole user and post objects for every feed entry would explode memory. Keep just the IDs, with a configurable limit per feed. Few users scroll back through thousands of posts, so the cache miss rate stays low.

For example, after Alice (followed by Bob and Dave) publishes post 105:

post_iduser_id (feed owner)
105bob
105dave
103bob
……

Newsfeed retrieval deep dive​

flowchart LR
U[User] -- "1. GET /v1/me/feed" --> LB[Load balancer]
U -- "media" --> CDN[CDN]
LB -- "2" --> WS[Web servers] -- "3" --> NFS[News feed service]
NFS -- "4. post IDs" --> NFC[(News feed cache)]
NFS -- "5. hydrate" --> UC[(User cache)] & PC[(Post cache)]
UC --> UDB[(User DB)]
PC --> PDB[(Post DB)]
NFS -- "6. JSON" --> U
  1. The user requests GET /v1/me/feed.
  2. The load balancer forwards it to a web server.
  3. The web server calls the news feed service.
  4. The service gets the list of post IDs from the news feed cache.
  5. A feed is more than IDs: it needs usernames, profile pictures, post text, and images. So the service hydrates each ID by fetching the full user and post objects from the user cache and post cache.
  6. The fully hydrated feed goes back to the client as JSON.

Media (images and video) is served from a CDN.

Practice: a hybrid fanout feed​

Build FeedSystem:

  • publish(author, post_id):
    • If the author has more than threshold followers, they're a celebrity. Append the post to the author's outbox and return 'pull'.
    • Otherwise, push: for each follower who hasn't muted the author ((follower, author) not in muted), insert post_id at the front of that follower's feed list and trim the list to limit. Return 'push to N', where N is the number of feeds written.
  • get_feed(user, k): take the user's pushed feed, add posts from the outboxes of celebrities they follow (unless muted), and return the top k post IDs in descending order. Post IDs are time-ordered, as with Snowflake IDs from Lesson 7, so higher means newer.

Input: line 1 is threshold limit, line 2 is the followers as author:f1,f2,… pairs, and line 3 is the mutes as viewer:author pairs (or -). Each remaining line is post <author> <id> or feed <user> <k>.

In the second test, Bob's cached feed holds only the newest 3 pushed posts (105, 104, 103), so post 101 has been trimmed away. That's the configurable limit at work: a user scrolling that far back would trigger a cache miss and a database read. Carol muted Alice, so none of Alice's posts reach her.

Practice: hydrate the feed​

Implement hydrate(post_ids, posts, users). It turns feed IDs into display rows:

  • posts[post_id] is (author_id, text), and users[author_id] is the author's display name.
  • Skip IDs missing from posts. A post may have been deleted after it was fanned out.
  • Return lines formatted as "<post_id> | <name> (@<author_id>): <text>", in the given order.

Input: line 1 is the feed's post IDs, each line user <id> <Name> adds a user, and each line post <id> <author_id> <text…> adds a post.

Cache architecture​

Caching is critical for a news feed. The book splits the cache tier into five layers:

LayerHolds
News feedFeed IDs per user
ContentPost data. Popular posts live in a hot cache
Social graphFollowers and following relationships
ActionWhether a user liked, replied to, or took another action on a post
CountersLike, reply, follower, and following counts
flowchart TB
subgraph Cache["Cache tier"]
direction TB
NF["News feed: feed IDs"]
CT["Content: hot cache · normal"]
SG["Social graph: follower · following"]
AC["Action: liked · replied · others"]
CN["Counters: like · reply · other"]
end

Splitting by kind of data lets each layer use its own eviction policy, size, and scaling. Hot celebrity posts in the content layer get far more traffic than a normal post, for example.

Check: fanout and retrieval​

Quiz

Push, pull, and hybrid

1 / 5

A user with 5,000 friends posts once. Under pure fanout on write, how many news-feed cache writes happen?

Pick an answer to see if you are right.


Step 4: Wrap Up​

We designed the two flows: feed publishing and news feed retrieval. There is no perfect design. Every company has its own constraints, so understand the trade-offs of each choice. With time left, discuss scaling:

Scaling the database:

  • Vertical vs. horizontal scaling
  • SQL vs. NoSQL
  • Primary–replica replication and read replicas
  • Consistency models
  • Sharding

Other talking points:

  • Keep the web tier stateless.
  • Cache as much as you can.
  • Support multiple data centers.
  • Loosely couple components with message queues.
  • Monitor key metrics, such as QPS at peak hours and latency when users refresh their feeds.

These are all covered in Lesson 1.


Key Takeaways​

  • Two flows: publish (post service + fanout service + notifications) and retrieve (news feed service + hydration).
  • Push gives fast reads but expensive writes for big audiences and waste for inactive users. Pull gives cheap writes but slow reads. The hybrid pushes for most users and pulls for celebrities.
  • The fanout pipeline: graph DB → user cache and settings filter (mutes) → message queue → fanout workers → news feed cache.
  • Feed caches hold IDs only, capped to a limit. Hydrate from the user and post caches at read time, and serve media from a CDN.
  • Split the cache into news feed, content, social graph, action, and counters layers.

Previous lesson: Design a Notification System.

Next lesson: Design a Chat System — WebSocket, stateful chat servers, KV-store chat history, multi-device sync, and presence.


Reference Materials​

  1. How News Feed Works — Facebook Help Center
  2. Friend of Friend recommendations with Neo4j and SQL Server