Design the News Feed
- Scope News Feed as a Product Architecture problem: data model and API, then pagination
- Walk write (posting) and read (viewing the feed) end to end, and decide where the feed actually gets built
- Defend your choices on fan-out on write vs read, the celebrity hybrid, ranking, cursors, and caching
Understanding the problem
"Design the News Feed" built the company, and few prompts come up as often in a Meta design round. The task sounds small. A user opens the app, you show them a scrollable list of recent posts from the people and pages they follow, freshest and most relevant first. One question hiding inside that sentence decides the whole round. When someone posts, do you deliver it then, or wait until each follower asks?
Start with the numbers, because they pick the answer for you. Call it 3 billion people using the app each month, and on the order of 500 million writing a post on a given day. Against that, every one of those people opens the feed several times a day and scrolls, so reads run into the hundreds of billions a day. Reads beat writes by something like a thousand to one. Lead with that ratio early. A feed is a read-heavy product, and the design bends toward making the read cheap even when that makes the write expensive.
Functional requirements
Pitch a narrow scope, then pause so the interviewer can steer you somewhere else. It's a short list. A user follows other users and pages. A user creates a post. A user opens their feed and pages through recent posts from everyone they follow, newest and most relevant first.
In scope
- Follow / unfollow a user or page
- Create a post (text, maybe an image link)
- Read your feed: a paginated list of posts from who you follow
- Rank the feed so the best posts surface first
Below the line (call it out, then skip)
- Likes, comments, and shares (an engagement subsystem of its own)
- The social graph service itself (assume it answers "who do I follow")
- Media storage and delivery (covered in the Instagram design)
- Notifications, search, ads, and stories
Non-functional requirements
Attach a target to each one, because these decide the architecture. Opening the feed has to feel instant, so aim to return the first page in a couple hundred milliseconds even at p99. Reading matters far more than writing, so we'll happily spend extra work when a post is created to keep the read cheap. The feed can be eventually consistent. If your friend's post takes a few seconds to show up, nobody notices, and that small slack buys enormous headroom. Availability beats strict freshness here; a feed that is a little stale but always loads beats one that is perfectly fresh but sometimes spins.
Flag one distribution now, because a later deep dive turns on it. Follower counts are wildly lopsided. A normal person has a few hundred followers; a celebrity or a big page has tens of millions. Any plan that treats those two the same breaks on the second kind. Hold that thought.
- ~3B Monthly users. The audience the feed serves.
- ~500M Posts / day. The write path.
- ~1000:1 Read : write. Why we precompute feeds.
- ~200ms Feed-load target. First page, even the slowest 1%.
The set-up
Core entities
Before any boxes go on the board, name the objects the system passes around. Leading with them is exactly what a Product Architecture round rewards. Four matter:
User
A person or page: an id, a display name, a follower count. The social graph (who follows whom) hangs off it but lives in its own service we treat as a given.
Post
One piece of content: an id, the author id, the text, an optional media URL, and a created-at timestamp. This is what flows through the system end to end.
Follow
A directed edge from follower to followee. The set of edges pointing *out* of you is "who I follow"; the set pointing *in* is "my followers". The feed needs both directions.
FeedEntry
A precomputed pointer that says "post P belongs in user U's feed," with a ranking score. The list of these for one user, in order, IS that user's feed.
Separate the post itself from the feeds it lands in. A Post is written once and read by every follower, so it lives once in a posts table keyed by post id. A FeedEntry is the opposite: cheap, disposable, and there are billions of them, one per (post, follower) pair we choose to materialize. Storing the full post text in every follower's feed would duplicate a megabyte of content across ten million feeds. So a feed holds only post ids plus a score, and the reader joins back to the posts table to get the text. Posts go in a store built for lookups by key. Reach for Cassandra, a wide-column store that spreads rows across many machines by a hash of the key. So writes never hit one hot machine. And a dead node loses nothing, because every row is copied to two others. Shard the posts table by post_id and the feed table by user_id, so one user's whole feed sits together on one shard and reads in a single trip. Spell out those keys. Interviewers ask.
API
Keep the surface thin. The feed read returns a cursor instead of a page number. A cursor is an opaque token that means "resume right after the last post I gave you," the only paging scheme that survives a feed where new posts arrive constantly. Deep dive three is entirely about why. The client sends the cursor it got last time, never a page index.
High-level design
Take the system one functional requirement at a time, building the simplest version first and calling out each bottleneck as it surfaces. The design splits along the read/write asymmetry: a write path that takes a new post and decides where it should land, and a read path that is mostly a cache lookup. In the diagram, each component's color signals its role: blue for people, violet for our services, amber for queues and streams, and green for storage and caches. The walkthrough below carries the same color on each component.
1) Create a post. The Author's client calls POST /posts against the Post service. It does two cheap things and returns: it writes the Post row to the Posts DB, and it drops a delivery job onto the Fan-out queue. The author's call does not touch a single follower's feed; it just records the post and hands the heavy lifting to the queue. So posting feels instant even for someone with a million followers, because the response comes back the moment the post is durably stored. We're punting on what that delivery job really does, and that is the first and biggest deep dive.
2) Deliver the post (fan-out). The Fan-out workers pull each delivery job off the Fan-out queue. For a normal author, a worker looks up that author's followers and writes one tiny Feed cache entry per follower: the post id and a score, prepended to each follower's feed list. This is "fan-out on write," doing the spread-out work when the post is created so that reading later is trivial. Putting it behind a queue lets the write path absorb bursts. A celebrity posting can owe ten million entries, and those simply wait as jobs while the worker fleet drains them, instead of blocking the author. The catch is that fanning a celebrity post out to tens of millions of feeds is enormous wasted work, since most of those followers won't open the app before the post is stale. Deep dives one and two size that problem and then fix it.
3) Read the feed. This path runs roughly a thousand times for every post, so it has to be cheap. The Viewer calls GET /feed and the Feed service reads their precomputed entry list straight out of the Feed cache in one in-memory lookup. It returns a page of post ids and scores already in order. Because the entries were built at write time, the common read does almost no work. The service then does two more things. It pulls the recent posts of the handful of big accounts this viewer follows directly from the Posts DB and merges them into the list by score. That is the celebrity hybrid we earn in deep dive two. Then it hydrates the page by fetching the actual post bodies for the top few ids it is about to return. Hydration is one batched read of maybe twenty posts rather than ten thousand, because we only fetch the bodies the viewer will see on this page.
4) Rank. A purely reverse-chronological feed is the naive version, and it is fine to start there. The real feed orders posts by a relevance score that goes beyond recency, so the post you are most likely to engage with sits near the top. For now treat the score as a number attached to each FeedEntry when it is written, and sort the list by it. How that score is computed, and whether it can even be precomputed, is deep dive four.
Technology choices
Up to now every box has been a category. In the room you name a concrete tool for each one and stand behind it. Meta interviewers drill the specific pick, and the follow-up is almost always a failure question: what happens when this server dies, or this queue backs up. Each pick below pairs why that category fits with how the design holds when it breaks.
- The Post service and Feed service are stateless servers behind a load balancer. No server holds anything a request needs, so any box serves any call and a dying one just drops out of the pool. All the state lives in the stores below. Boring is the goal for these.
- The Posts DB is Cassandra (or DynamoDB, the managed equivalent). A post is written once and read by key, billions of times. A wide-column store handles that well. It spreads rows across many machines by a hash of
post_id, so no single machine is hot, and it copies every row to two other machines so a dead node loses nothing. The follow-up writes itself, what about a viral post hammered by reads. Because the copies can all serve reads, a hot post just gets served from whichever replica is free, and a post body never changes after it is written, so a stale copy is impossible. - The Fan-out queue is Kafka. Delivery jobs land on a durable log split into partitions, parallel slices of the job flow, keyed by author so one author's posts stay in order. The log buys two things. A worker that dies mid-fan-out does not lose the job: Kafka remembers each worker's bookmark in the log, the offset, so a fresh worker resumes from where the dead one stopped. And when a celebrity post dumps ten million entries of work, the log absorbs the spike and the worker fleet drains it at its own pace instead of toppling. Replays after a crash can deliver a job twice, so writing a feed entry is made idempotent, keyed by (post, follower), and writing the same entry twice changes nothing.
- The Fan-out workers are a plain autoscaled worker fleet that grows with queue depth. Their one job is the follower lookup plus the per-follower write. Take the poison job, an author whose follower list is somehow corrupt and crashes every worker. After a few failed tries it lands in a dead-letter queue, a side queue for jobs that keep failing, instead of cycling forever.
- The Feed cache is Redis, holding each user's feed as a sorted list of (post id, score) capped at a few hundred entries. Redis is in-memory, so the read is microseconds, and the cap matters: nobody scrolls past a few hundred posts, so keeping more is wasted memory. The failure question is what happens when a feed isn't in the cache, either evicted or on a cold node. Because the feed is rebuildable, on a miss the Feed service reconstructs that user's recent feed from the Posts DB and refills the cache, so losing a Redis node costs latency, never data.
The feed cache and the posts DB play different roles, and it pays to keep them straight. The posts DB is the durable record, the one source of truth that must never lose a post. The feed cache is a disposable, rebuildable index built from that record to make reads fast. Treating the cache as throwaway is what lets us run it lean and in memory. Mix the two roles up, trying to make the cache authoritative or the database fast enough to serve every scroll, and you've found one of the most common ways this design goes wrong. The brand matters less than the defense here: "a Cassandra-style wide-column store" works fine, and what counts is knowing why that category fits.
Potential deep dives
This is where most of the interview signal sits. The interviewer seizes on one or two pieces and pushes hard. Below are the ones Meta returns to most, the fan-out decision above all, each scored against the tiers an interviewer grades you on.
1) Do we build the feed when you post, or when you read?
This is the heart of the problem, and the question Meta probes hardest. Building a feed is a join: take everyone you follow, take their recent posts, merge and sort. The only real choice is when to pay for that join. Pay at write time, pushing each new post into every follower's feed the moment it is created, and reads are free. Pay at read time, gathering posts from everyone you follow when you open the app, and writes are free. Size both. A read happens about a thousand times per post, so a scheme that makes reads cheap and writes expensive pays the cheap side a thousand times less often. That's the instinct. The numbers are about to complicate it.
Approach
Store nothing per follower. When a user opens the app, look up everyone they follow, query each one's recent posts, merge the lists, sort by score, and return a page. Writes are trivial: posting is a single insert and touches no follower.
Challenges
The read is brutal. A user following 500 accounts now fans a single feed-open into 500 queries, merged and sorted on the fly. And this fires on the busiest path in the system, hundreds of billions of times a day. You have moved the expensive join onto the read, which is exactly the side that runs a thousand times more often. Latency and database load both explode. Nobody ships pure fan-out on read, but you should be able to say exactly why it fails.
Approach
When a post is created, push a small entry into every follower's Feed cache list right away, as the high-level design does. The read is now a single cache lookup of an already-built, already-sorted list, which is what hits the ~200ms target. The write does the join work up front, once per post, instead of once per open.
Challenges
Two problems. The obvious one: a celebrity with 10 million followers turns one post into 10 million feed writes, a "fan-out storm" that floods the queue and the cache every time they post. The subtle one: most of those 10 million followers will not open the app before the post ages out, so you spent millions of writes building feed entries nobody reads. Pure fan-out on write does enormous wasted work on exactly the accounts that post most visibly.
So neither pure scheme works. Fan-out on read drowns the common case; fan-out on write drowns on the rare account with millions of followers. The lopsided follower distribution we flagged earlier is what forces the upgrade. Use a different scheme for each kind of account. That hybrid is the answer the round is built around.
Approach
Split authors by follower count. For normal accounts, below some threshold like 100K followers, fan out on write: their posts get pushed into followers' feeds as before, and since their follower lists are small the write is cheap. For high-follower accounts, do NOT fan out on write at all. Their posts just sit in the Posts DB. Then at read time, the Feed service reads the precomputed feed from the cache, which already holds everything from the normal accounts you follow. Separately, it pulls the recent posts of the handful of big accounts you follow directly, then merges the two by score. A viewer follows maybe a dozen celebrities, so that read-time pull is a dozen small queries, not 500. You have made the common write cheap AND the common read cheap, by refusing to fan out only on the accounts where fan-out would be wasteful.
Challenges
It costs a threshold to tune and two code paths to keep correct, plus the merge logic on read. The threshold is a real dial: too low and you push read-time work onto everyone; too high and the biggest accounts still cause storms. A clean answer states the threshold, explains the merge, and notes that an account near the line can go either way. Get to the split before you're asked and the hard part of the round is behind you.
2) How big is the celebrity fan-out really, and where does the work go?
Size it before solving it. Suppose 500 million posts a day come from normal accounts, and the average normal account has about 300 followers. That's 500M times 300, roughly 150 billion feed-entry writes a day from fan-out on write, which spread over a day is about 1.7 million writes a second. A Redis-backed cache fleet handles that fine when it's sharded across enough machines. Now drop in the celebrities. If a single account with 50 million followers posts, that one post owes 50 million writes. A handful of huge accounts posting in the same minute would, under pure fan-out on write, dwarf the entire normal-traffic rate in a few seconds. That arithmetic is why the hybrid exists at all.
Approach
Keep fanning celebrity posts out on write, but protect the system. Cap each follower's feed list at a few hundred entries so the cache memory stays bounded. Rate-limit how fast a single huge fan-out drains off the queue so one celebrity post cannot starve everyone else's delivery. The queue smooths the spike over seconds instead of letting it hit all at once.
Challenges
You are still doing 50 million writes for one post, most of them for followers who never open the app that day. The spike is only smoothed. The wasted work remains. Worse, a celebrity post that should appear quickly now drains slowly behind the rate limit, so the people who DO want it see it late.
Approach
For accounts above the follower threshold, do not write feed entries at all. Their recent posts live only in the Posts DB, indexed by author and time, so "the last few posts by account X" is one cheap query that grabs a contiguous slice of rows. When a viewer opens their feed, the Feed service reads the precomputed list (everything from the normal accounts). In parallel, it runs that range query for each big account the viewer follows, then merges by score. The viewer follows a dozen big accounts at most, so this adds a dozen small reads to a path that was already fast, while removing tens of millions of writes per celebrity post. Cache the big accounts' recent-posts lists in Redis too, since thousands of viewers pull the same celebrity's posts in the same minute. Now a celebrity post is one DB write plus one shared cache entry that every follower reads.
Challenges
The merge has to interleave two sources by score and stay correct across pages, which the cursor in deep dive three has to account for. And the threshold needs maintenance as accounts grow across it. The payoff is decisive: write cost stops scaling with the biggest accounts' follower counts, which is the property that keeps the system stable when a major event makes everyone post at once.
3) How does pagination work when new posts keep arriving?
A feed is a moving target. While you scroll, new posts arrive at the top, so the list isn't a fixed array you can index into. Picture asking for "page 2" of a feed that grew by ten posts since you loaded page 1. Page 2 now starts ten posts earlier than it did, so you'd see the bottom of page 1 again, the classic duplicate-and-skip bug that plagues offset paging on live data. So page by where you are rather than by which page number.
Approach
GET /feed?page=2&size=20 skips the first 40 entries and returns the next 20, the way a static search-results list works.
Challenges
On a feed that changes under you, the offset points at a different post every second. New posts at the top shove everything down, so page 2 overlaps page 1 (duplicates) or jumps past unseen posts (gaps). And in the database, "skip 40" still means scanning and discarding 40 rows, so deep pages get slower the further you scroll. Wrong tool for a live, append-at-the-top list.
Approach
The feed read returns a nextCursor, an opaque token encoding the position of the last entry it gave you. In practice that is the score and id of that last post, base64-encoded so the client treats it as a black box. The next request sends that cursor back, and the server returns "the entries ranked just after this one," a direct range read from that position with no skipping. New posts arriving at the top do not move your cursor, because the cursor names a specific post. You keep scrolling cleanly past where you were while fresh posts pile up above you. To merge in the celebrity reads from deep dive two, the cursor encodes the position in both sources, so the next page resumes correctly across the precomputed list and the celebrity range-query at once.
Challenges
Cursors only walk forward, so you cannot jump to an arbitrary page, which a feed never needs anyway. The cursor must encode whatever the list is sorted by. If ranking sorts by a score, the cursor carries the score plus id as a tiebreaker, so two posts with the same score never blur the boundary. And a "pull to refresh" for brand-new posts at the top is a separate query with no cursor, returning everything newer than the user's current top.
4) How do we rank the feed, and can the score be precomputed?
Reverse-chronological is the honest starting point, and you should name it first. Sort every feed entry by post time, newest on top; it's simple, and it's what every feed started as. The trouble is that the best post for you is often an older one. A pure time sort buries the post you'd most want to see under a pile of fresher but duller ones. Ranking reorders the feed by a relevance score that predicts how likely you are to engage. The hard part for the architecture: this score depends on both the post and the viewer, the opposite of what fan-out on write assumed.
Approach
When fan-out writes a feed entry, compute a score then and store it alongside the post id, so the read just sorts by a number already in the list. Use signals available at write time: the post's own recency, the author's typical engagement, the strength of the follow relationship.
Challenges
The score is frozen at write time, so it cannot use anything that changes after, like how the post is performing right now or what the viewer just engaged with this session. A post that goes viral an hour after you got it keeps its stale low score and stays buried. And for the celebrity reads, which were never fanned out, there is no precomputed score at all, so those have to be scored some other way regardless.
Approach
Split the read into two stages. First, *candidate generation*: gather a few hundred posts the viewer could see, cheaply, from the precomputed feed list plus the celebrity range-queries. Then *ranking*: score just those few hundred candidates at request time with a model that takes the freshest signals, and sort by the result. Those signals include the viewer's recent activity, the post's live engagement, and time of day. Scoring a few hundred candidates per open is affordable because the candidate set is already small; you never score all of a user's possible posts. This is how a real feed works, and it cleanly handles the celebrity posts too, since they enter ranking as just more candidates.
Challenges
Read-time ranking adds latency and a model-serving dependency on the hot path, so the model has to be fast and is usually a lightweight scorer over precomputed features, with the heavy machine-learning kept offline. The feed then reflects what is happening now, which directly moves the engagement number the product round cares about. It also shows you know ranking belongs at read time, where the freshest signals live.
5) How does caching keep the read path fast and consistent?
The read path leans almost entirely on the Feed cache, so its behavior under stress decides whether the feed feels instant. Two questions come up. What happens on a cache miss, and how do edits or deletes stay consistent when a post lives in millions of cached feeds at once.
A miss means a user's feed list has been evicted or sits on a cold cache node, and the Feed service rebuilds it. It reads that user's follows, pulls the normal accounts' recent posts from the Posts DB and merges them with the celebrity reads. It writes the rebuilt list back to the cache and serves it. The feed cache is rebuildable from the durable posts store, the property that lets us treat it as disposable. The risk here is a cache stampede: a popular shard goes cold and thousands of users miss at the same instant, all rebuilding the same data and hammering the database together. The fix is request coalescing, also called single-flight: the first miss for a given feed starts the rebuild and every other request for it waits on that one rebuild instead of launching its own.
Consistency on edits and deletes is the subtler half. A post edited or deleted after fan-out already sits as an id in millions of feed lists. Rewriting all those lists would be a second fan-out storm, so we don't. A feed entry stores only the post id, and the body is hydrated from the Posts DB at read time. So an edit just updates the one posts row, and every feed picks it up on its next hydration at no extra cost. A delete sets a tombstone, a small "this post is gone" marker on the posts row. The feed service drops tombstoned ids while hydrating a page, so the stale pointer in a thousand caches resolves to nothing and ages out on its own. Storing pointers rather than bodies in the feed is what makes edits and deletes cheap. For the general caching patterns underneath this, the scaling reads lesson goes deeper.
What is expected at each level
That's a lot of surface area. How much of it you need to land depends on the level you're interviewing for. The design round mostly shows up at E5 and above, and it's the round that most often decides your level.
E4 (mid-level)
Mostly breadth, roughly 80/20 breadth to depth, and at E4 a design round is light or absent. Get to a working end-to-end design: a posts table, a feed cache, fan-out on write, a cursor-paged read. You should know what each box does when probed even if you don't reach the optimal version of any of them, and the interviewer will drive the deeper parts. The bar: a complete skeleton you can explain, with a few rough edges the interviewer helps you smooth.
E5 (senior)
Breadth, then real depth, roughly 60/40. You're expected to reach the celebrity hybrid on your own, naming the lopsided follower distribution and deriving fan-out on write for the many plus fan-out on read for the few before being asked. Reach for cursor pagination without prompting, and defend the posts and feed partition keys. The bar: a near-optimal design where you found the celebrity bottleneck yourself. This is the level the design round most often decides, and reaching the hybrid on your own is usually what tips it.
E6+ (staff)
Depth across several components and a widening scope, roughly 40/60. Expect questions on read-time ranking as a two-stage problem: gather a few hundred candidate posts cheaply, then score them. Expect questions on consistency when posts are edited or deleted across millions of cached feeds. Expect the stampede question, when many users miss the cache at once and how the feed rebuilds. And expect to be asked how the system degrades when the fan-out queue backs up during a major event. You're driving the conversation and reasoning about operational reality well past the happy path, including the product-judgment turn on engagement tradeoffs. The bar: you lead, and the interviewer steps in to redirect rather than to rescue.
Key Takeaways
- ✓Lead with the data model and API, this is a Product Architecture round: a post is stored once, and feeds hold cheap (post id, score) pointers hydrated at read time.
- ✓The problem comes down to when you pay for the join. Fan-out on write makes reads cheap; the hybrid skips it for high-follower accounts and reads them at fetch time, and that hybrid is what the round is built to surface.
- ✓Page with cursors instead of page numbers, so new posts arriving at the top never duplicate or skip; the cursor encodes the merge across precomputed and celebrity sources.
- ✓Ranking is per-viewer and read-time: generate a few hundred candidates cheaply, then score those with fresh signals. The feed cache is a rebuildable index built from the durable posts store, which stays the source of truth.
Meta interview guideKnow what Meta asks before you walk in.
28 coding · 5 system design · 28 behavioral questions
Get their questions first.
InterviewLegend is independent and not affiliated with or endorsed by any company it covers.