MediumProblem 7 of 36 in the system design series
Design Facebook News Feed: Fan-Out, Hybrid Feeds and Viral Posts
Prefer watching? This article is the written companion to the video above, from the Shubham Aher YouTube channel.
A news feed is the scrolling list of recent posts that a social app assembles separately for each person, drawn from the friends they are connected to and the pages they follow. Every user sees something different, and every refresh is expected to return quickly, so the feed is effectively a personalized query that runs billions of times a day.
Interviewers like this question because the naive answer looks reasonable on a whiteboard and then collapses under arithmetic. Querying every friend at read time is too slow, while copying every post into every follower's feed explodes for accounts with tens of millions of fans. The problem forces you to reason about read and write amplification, not just draw boxes.
The second trap is popularity. A single post that suddenly attracts millions of views and likes concentrates traffic on one cache key and one counter. A strong answer for a Facebook-like service shows how the design spreads that load out, and where it is acceptable to trade exactness for survival.
This is an interview practice design for a Facebook News Feed-like service, built from first principles. It is not a description of how Facebook News Feed is actually built.
Functional Requirements
The scope is deliberately small: people can publish a post containing text plus an optional photo or link, build a social graph, read a feed, and like posts. Keeping the list tight lets the conversation focus on the feed itself instead of drifting into comments, ads or recommendation models, which are each a separate system.
The relationship requirement deserves attention because it has two shapes. Friendship is mutual, so both people see each other's posts, while following a page or creator is one-directional. That distinction matters later: a person has a bounded number of friends, but a page can collect an unbounded number of followers, and that asymmetry is what drives the fan-out decision.
Create a post
Text, plus an optional photo or link.
text · photo · link
Friends & follows
Friend people, or follow pages and creators.
two-wayone-way
Scroll the feed
Posts from friends and pages, newest first.
endless scroll
Like a post
Like posts and see the like count.
counts
- Out of scope today: comments, ads, ML ranking models
Non-Functional Requirements
Feed loads are the most frequent action in the product, so the first page should render in under 500 ms. That budget rules out any design that has to scan hundreds of sources on every open. It pushes us toward precomputed results that can be fetched with a single lookup.
We choose availability over strict consistency. If a friend's new post shows up in your feed thirty or sixty seconds late, nobody notices, but an error screen is immediately visible. Accepting that delay is what allows post delivery to run asynchronously in the background. The last two requirements, a 2 billion daily user read load and resilience to viral posts, are the pressures the deep dives are built around.
Fast feed loads
The first page appears almost instantly.
< 500 ms
Availability first
A new post may take up to a minute to reach every feed.
eventual consistency
Huge read scale
Billions of users load the feed many times a day.
2B DAU
Survive viral posts
One post can get millions of reads and likes.
hot keys
Scale Estimation
Assume roughly 2 billion people use the app daily and refresh their feed around 10 times apiece. That works out to around 20 billion feed loads daily, or about 230K per second on average, with peaks well above that. Reads clearly dominate.
Writes look modest at first. If around a quarter of users post once a day, we get roughly 500 million posts, close to 5.8K per second. The number that changes the design is what happens after a post is written. With an average of about 300 friends, delivering each post into every friend's feed means roughly 1.7 million feed insertions per second. Fan-out, not storage or raw post volume, is the expensive part, so the core design question becomes which authors get pushed to their audience and which get pulled on demand.
2billion
Daily active users
≈ 10 feed loads each / day
230K avg
Feed loads per second
20 billion / day
5.8K
New posts per second
500 million / day
1.7million
Fan-out writes / sec
5.8K posts × 300 friends
Fan-out is the real cost → choose who is pushed and who is pulled
Core Entities & API
Four entities cover the model: User, Post, the Friendship or Follow edge, and Like. Keeping the relationship as its own entity makes it easy to store both mutual and one-way edges in the same graph store and query either direction.
The API has three calls. POST /posts accepts text, an optional media URL that points at already-uploaded content, and a privacy setting, then returns the new post ID. POST /posts/{id}/like records a like and returns the current count. GET /feed takes a cursor and a limit of around 20 and returns a page of posts plus the next cursor. Cursor pagination is chosen over page numbers because the feed changes constantly; an offset would shift as new posts arrive and the user would see repeats or gaps.
Core entities
UserPostFriendship / FollowLike
API
- POST /posts { text, media_url?, privacy }→ { post_id }
- POST /posts/{id}/like→ { like_count }
- GET /feed?cursor={c}&limit=20→ { posts[], next_cursor }
High-Level Design
Requests enter through an API gateway that authenticates the caller, applies rate limits and routes the call. The write path goes to a post service, which stores the post in a posts database partitioned by post ID, emits a post-created event to Kafka and responds to the author without waiting for delivery. Decoupling the response from distribution is what keeps posting quick even for someone with thousands of friends.
Fan-out workers consume those events, look up the author's audience in the social graph and append the new post ID to each recipient's feed list in a Redis feed cache. Storing only IDs keeps each list small, and an edit to a post never requires touching millions of feed entries.
The read path is handled by a feed service. It reads this person's cached ID list, then resolves those IDs into full posts with one batched request to a post cache. That post cache also holds the latest posts from large pages and creators, which the next deep dive relies on.
Deep Dive 1 — Push vs Pull
Pull on read means the feed is assembled when the user opens it, by querying recent posts from every friend and merging them. Posting costs nothing extra, but at around 230K feed loads per second, each requiring about 300 lookups, the read side drowns. It also makes the 500 ms target very hard to hit.
Push on write flips the cost. Each new post is appended to every follower's precomputed feed, so a read becomes a single fetch. For ordinary users that is fine, since friend counts are capped at a few thousand. The problem is a celebrity page with 50 million followers: one post would trigger 50 million writes, many of them for people who will not open the app this week.
The hybrid approach takes the best of both. Posts from regular users and smaller pages are pushed, while posts from very large accounts are kept once in a per-author recent-posts list and merged in when a follower loads the feed. Since a typical person follows only a few such accounts, that merge touches a small number of lists. Write cost stays bounded, reads stay fast, and because delivery runs through Kafka, the author never waits for it.
Pull on read
- + Writes are free
- − 300 queries per feed load
- − Too slow at 230K loads/s
Push on write
- + Feed is precomputed
- − 50M followers → 50M writes
- − Wasted on inactive users
Hybrid push + pullChosen
- + Bounded write cost
- + Fast reads for everyone
- − Merge step at read time
- Push: friends and small pages · Pull: pages with millions of followers
- Fan-out is async via Kafka → posting feels instant
Deep Dive 2 — Serving the Feed
Serving one feed page is a short pipeline. The feed service first reads the precomputed list of post IDs for this user, which is trimmed to a few hundred recent entries. It then fetches the recent-posts lists of any large accounts the user follows and merges the two sources into one ordering. Today that ordering is by time, but this merge point is exactly where a ranking model would later slot in without changing anything around it.
Before returning, the service filters out posts that were deleted, hidden by the user, or are no longer visible because of a privacy change. Checking visibility at read time means that tightening a post's audience takes effect on the very next load instead of waiting for a cleanup job to scrub millions of cached lists.
Finally the service hydrates the surviving IDs. Rather than issuing 20 separate lookups for 20 posts, it sends one multi-get to the post cache and gets text, author details and like counts in a single round trip. The response includes a cursor pointing just past the last item returned, so the next scroll resumes from a stable position and never repeats a post.
- 1Read feed IDsPrecomputed post IDs from the Redis feed cache
- 2Pull big accountsRecent posts of followed pages and creators
- 3Merge and sortBy time today; a ranker can plug in here later
- 4FilterDrop deleted, hidden or privacy-blocked posts
- 5Hydrate in one batchOne multi-get from the post cache, not 20 calls
- 6Return with a cursorNext page starts after the last item seen
Deep Dive 3 — Viral Posts
A viral post creates hot keys on both sides. On the read side, millions of feeds request the same post at once and the cache node that owns it becomes a bottleneck. The fix is to replicate hot entries across several cache nodes and keep a tiny in-process cache on each feed server, so most reads never leave the machine.
Likes are the write-side version of the same problem. One counter row cannot absorb that rate of increments, so the count is divided across N sub-counters, increment a random one, and sum them when reading. A further step is to send likes through Kafka and let a consumer aggregate them for a few seconds before writing a single total, turning thousands of writes into one.
When a popular entry expires, a wave of simultaneous misses can stampede the database. Request coalescing lets one request rebuild the value while the others wait for its result. All of this relies on a product judgement: a like count that is approximate and a few seconds stale is perfectly acceptable to users.
Replicate hot posts
Copy viral posts to several cache nodes, plus a tiny local cache per server.
Sharded like counters
Split one counter into N shards; increment a random one, sum on read.
Batch likes through Kafka
Add up likes for a few seconds, then write one total, not thousands.
Request coalescing
On a miss, one request rebuilds the cache entry; the others wait for it.
Approximate counts are fine
Nobody checks if 1.2 million likes is exact to the second.
Trade-offs
Each choice in the design has a cost that should be stated openly. Hybrid fan-out bounds write volume and keeps reads fast, but every feed load pays for a merge step. Checking privacy at read time makes changes take effect immediately, at the price of extra filtering work on each request.
Capping cached feeds at a few hundred IDs keeps memory predictable and reads quick, but a user scrolling far into the past needs a slower path that falls back to the database. Approximate like counts let the system ride out viral spikes, with the accepted downside that numbers trail reality by a few seconds. The guiding rule: push to the many ordinary authors, pull from the few huge ones, and never let one hot key carry all the load.
Hybrid fan-out
Bounded writes and fast reads — but a merge step on every feed load.
Privacy checks at read time
Changes apply instantly — but every feed load does extra filtering.
Cached, capped feeds
Fast and memory-bounded — very old posts need a slower fallback.
Approximate like counts
Survives viral spikes — but counts lag by a few seconds.
Push to the many. Pull from the few. Never trust one hot key.
Key takeaways
- Fan-out, not post storage, is the dominant cost: about 1.7 million feed writes per second at the assumed scale.
- Use hybrid fan-out: push posts from regular users, pull posts from accounts with huge follower counts.
- Store only post IDs in feed caches and hydrate full posts with one batched multi-get.
- Filter for privacy and deletion at read time so visibility changes apply on the next load.
- Handle viral posts with replicated hot keys, sharded or batched like counters, and request coalescing.
Interview questions
Should a news feed use push or pull fan-out?
Most large designs use a hybrid. Posts from ordinary users are pushed into followers' precomputed feeds, which makes reads a single lookup. Posts from accounts with millions of followers are pulled instead: stored once in a recent-posts list and merged into a follower's feed when it is opened. This bounds the write cost of celebrity posts while keeping feed reads fast for everyone.
How do you handle a page with 50 million followers in a feed system?
Do not push its posts to every follower. Keep the page's latest posts in a single cached list and merge them into each follower's feed at read time. Because a typical user follows only a handful of such large accounts, the read-time merge stays cheap, and one post no longer turns into 50 million separate writes.
How do you count millions of likes on a single post?
Avoid a single counter row. Split the count across several sub-counters, increment a random one on each like, and sum them on read. Alternatively, stream likes into Kafka and have a consumer aggregate them over a few seconds before writing one total. Both approaches accept that the displayed count is approximate and slightly delayed, which users do not notice.
How does posting stay fast when a user has thousands of friends?
The post service only saves the post and publishes an event, then replies to the author immediately. Fan-out workers consume the event from Kafka and add the new ID to every friend's cached feed in the background. The author sees success in milliseconds, and friends receive the post over the following seconds, which fits the eventual consistency requirement.
Why use cursor pagination for an infinite scrolling feed?
New posts arrive constantly, so offset-based pages shift between requests and users see duplicates or miss items. A cursor marks the last item returned, and the next request continues strictly after it. That gives a stable position no matter how many new posts land at the top, and it is also cheaper for the backend to resolve.
Keep practising
- Learn the building blocks in the free system design course.
- Rehearse answers with system design interview questions.
- See which open jobs fit your skills: upload your resume.