Skip to lesson
‹

Courses / System Design Interview / Chapter 28

Module 3 · Design Real Products · Chapter 28 of 39

Design a Microblog

20 sections · ~39 min✓ Fact-checked

Builds on What System Design Is: From One Server to Millions, Back-of-the-Envelope Estimates, Databases: Replication, Partitioning and Choosing a Store, Content Delivery Networks, Unique IDs and Sequencers, Distributed Caches, Message Queues, Publish-Subscribe, Blob Stores, Distributed Search and Sharded Counters. Next up: Design a News Feed.

Part 1 · Requirements and Scale

The brief

9:52 pm: India hit the last ball of a World Cup final for six. Kabir, a former fast bowler turned commentator with 30 million followers, posts two words: "WHAT A FINISH." Two million fans post in the same minute. On a first-draft design, Kabir’s post alone means 30 million timeline writes, queued ahead of everyone else’s. Ravi, who follows Kabir and 299 other accounts, refreshes and sees nothing new. He refreshes again, and each refresh asks the database to rebuild his timeline from scratch. The trophy is lifted before his timeline catches up. This chapter builds the design that keeps up.

The product: a microblog where people post short public messages, follow other accounts and read a home timeline of the newest posts from everyone they follow. Plan for about 200 million daily users. A few accounts, like Kabir’s, have tens of millions of followers. You are designing the posting path, the follow graph and the home timeline.

What we are building

Before any numbers, pin down what the product does, because the hard part only shows once the scope is clear.

A microblog is a service for short public posts. People publish a few lines of text, follow other accounts and read a home timeline: the recent posts of everyone they follow, newest first. Following is one-way, so Ravi can follow Kabir without Kabir following him back. That is how one account collects 30 M followers while most have a few dozen.

Writing a post is easy: one row per post. Reading is the hard part. Every time someone opens the app, the system must show a fresh timeline stitched from the posts of hundreds of accounts, in a fraction of a second, for hundreds of millions of people. The whole design follows from making that read cheap.

The scope below keeps the timeline at the centre and leaves out features that would each bring their own write path or read path.

In scopeOut of scope
Post up to 280 characters, with an optional imageDirect messages
Follow and unfollow any accountAds and promoted posts
Home timeline: posts from accounts you follow, newest firstA ranked feed (Design a News Feed covers ranking)
Profile timeline: one account’s own postsEditing a post after publishing
Like and repost, with countsModeration pipelines in detail
Search posts by keyword; see trending topicsNotifications

Everything on the right can be added later without changing how the home timeline is built, which is why it can wait. The next question is how fast, how fresh and how safe the timeline must be.

How well it must work

Scope says what the product does; non-functional requirements say how well, and they decide the architecture. Each one below gets a number or a clear rule.

  • Fast reads. The home timeline returns in under 200 ms at the 99th percentile (p99), measured at the server: 99 of every 100 requests are faster than that.
  • Read-heavy. People scroll far more than they post: about 20 timeline loads for every post written, and each load shows 20 posts.
  • Available rather than strictly consistent. If Kabir’s post reaches a follower 5 seconds late, nobody is harmed. If timelines stop loading during the final, everybody notices. So timelines are eventually consistent: every copy catches up after a short delay.
  • Durable. Once the app says "posted", the post is never lost.
  • Read your own writes. Ravi always sees his own new post at once, even if his followers see it a few seconds later.

These assumptions feed every estimate in the chapter.

AssumptionValue
Daily active users (DAU)200 M
Monthly active users300 M
New posts per day100 M (half a post per active user)
Timeline loads per active user per day10
Average followers per account200
Accounts an active user followsAbout 300 (active users follow more than average)
Largest accounts30 M followers or more

Two rows matter most: 200 followers on average, and 30 M for the largest accounts.

The average hides the problem

Follower counts are very uneven: most accounts have a few dozen, while a few thousand have millions. A design sized for the average of 200 can still collapse on that long tail, so every choice in this chapter is checked against Kabir as well as Ravi.

Estimates: the fan-out multiplier

With the assumptions set, the question is which number decides the design. Start with the cheap one. 100 M posts a day is about 1,160 posts a second (100 M ÷ 86,400), the arithmetic from Back-of-the-Envelope Estimates. One well-tuned database could absorb that alone.

The expensive number hides one step further. If every post id is copied into each follower’s timeline, one post becomes 200 writes.

Animated sceneHow one post becomes 200 writes0:16

Fan-out workers at the peak: ≈ 60 (about 12 at the daily average; a queue absorbs bursts above 60)

Read it as steps

Watch for the multiply step: 1,160 cheap posts a second become over a million timeline inserts at the peak of the final.

The follower count, not the post rate, sets the size of the system. Fan-out turns 1,160 posts a second into more than a million timeline inserts at the peak, and it says those inserts must run in the background through a queue, because nobody should wait for 200 writes.

The other estimates are quick, and each answers a different question.

QuantityWorkingResult
Timeline loads200 M users × 10 ÷ 86,400 s≈ 23,000 /s; ≈ 115,000 /s at peak
Post text100 M posts × 1 KB a day≈ 100 GB/day; ≈ 37 TB/year, ≈ 110 TB with 3 copies
Images10 M images × 200 KB a day≈ 2 TB/day, kept in a blob store
Timeline cache300 M monthly actives × 800 ids × 8 bytes≈ 1.9 TB of raw ids; about 4 TB stored

Storage is ordinary; the timeline inserts and the timeline cache are what need design. The 1 KB per post is generous on purpose: 280 Devanagari characters take up to 840 bytes in UTF-8, at 3 bytes each, and the post also carries ids, a timestamp and flags.

Part 2 · API, Data and the First Design

The API

The estimates say reads dominate, so the API should keep both paths simple. A handful of calls covers every requirement, and the server learns who is calling from the auth token, never from a user id in the body.

http
POST   /v1/posts                 {"text": "What a finish", "media_ids": ["m_91"]}
       Idempotency-Key: 6f1c2a   -> 201 {"post_id": "1834092", "created_at": "..."}
DELETE /v1/posts/{post_id}       -> 204
PUT    /v1/follows/{user_id}     -> 204   (follow; repeating it is harmless)
DELETE /v1/follows/{user_id}     -> 204   (unfollow)
GET    /v1/timeline/home?limit=20&cursor=1834092  -> 200 {"posts": [...], "next_cursor": "1829117"}
GET    /v1/users/{user_id}/posts?limit=20&cursor=...
PUT    /v1/posts/{post_id}/likes/me              -> 204
GET    /v1/search?q=last+over&cursor=...

Images are uploaded to the blob store first; the post refers to them by media id.

Idempotency key. Phones on crowded stadium networks time out and retry. The app attaches a random key to each new post; if the server has seen that key in the last 24 hours, it returns the original result instead of creating a second, identical post. Without it, every timeout during the final becomes a duplicate.

Cursors, not page numbers. New posts arrive at the top while Ravi scrolls, so "page 3" points somewhere different every second and he sees the same posts again. A cursor that says "posts older than id 1829117" is not moved by new posts at the top, so the next page carries on exactly where the last one ended. This works because the timeline is sorted by id; Design a News Feed shows what changes when the list is ranked instead.

PUT for follow and like

Following the same account twice should still leave one follow. HTTP defines PUT as idempotent: sending it again leaves the same state, so a retry after a timeout does no harm.

Each call reads a different shape of data. The next page decides where each shape lives.

Data model

Four kinds of data sit behind those calls, and each is read in its own way. Pick storage from the access pattern, as the Databases chapter recommends, not from habit.

usersuser_id (pk)handle (unique)namebiofollower_countcreated_at
postspost_id (pk, time-ordered)author_idtextmedia_idsreply_tocreated_atdeleted
followsfollower_id (pk part 1)followee_id (pk part 2)created_at
likespost_id (pk part 1)user_id (pk part 2)created_at

The table maps each question the system asks to the layout that answers it from one partition.

Question the system asksShape of the readWhere it lives
Fetch these 20 postsPoint lookups by idPost store (sharded relational), hash-partitioned by post_id, with a post cache in front
One author’s posts, newest firstRange scan by authorA user_posts index, partitioned by author_id and sorted by post_id
Who follows this author?Scan of up to millions of rowsA followers table, partitioned by followee_id
Whom does this user follow?Short list, read oftenA following table, partitioned by follower_id
This user’s home timelineNewest 800 ids, read constantlyTimeline cache, partitioned by user_id

The follow graph is stored twice, once by follower and once by followee, because fan-out asks "who follows Ravi?" while timeline rebuilds ask "whom does Ravi follow?". Two writes per follow is a small price for answering each from a single partition.

Ids that sort, keys that spread

Use time-ordered 64-bit post ids, as built in Unique IDs and Sequencers: sorting by id is then sorting by time, and the id doubles as a cursor. Do not range-partition the post store on that id, though. Every new post would land in the newest range, so one shard would take all 1,160 writes a second while the rest sat idle. Hash the id to pick the shard, and keep time order inside each author’s user_posts list.

High-level design

With the data placed, the system splits into two paths. The write path saves a post and hands a job to a queue. The read path serves a home timeline from a cache. Fan-out workers join the two: they read the author’s follower list and push the new post id into each follower’s cached timeline. Follow one of Ravi’s posts through it.

Ravi’s post, from save to screen
  1. 1The post service saves the post and an outbox row in one transactiona crash between the two cannot lose the fan-out job
  2. 2It adds the id to Ravi’s own timeline and replieshe sees his post at once, before any fan-out
  3. 3A relay moves the outbox row to the fan-out queue and the post streamsearch and trending read the stream
  4. 4A fan-out worker reads Ravi’s 200 followersfrom the followers table
  5. 5The worker inserts the id into each follower’s timeline listdone within a second or two
  6. 6A follower opens the app; the timeline service reads her list and hydrates 20 poststwo cache reads, about 16 ms on the server

Each component has one job:

  • Post service: assigns a time-ordered id and writes the post plus the outbox row on its shard, the outbox pattern from Message Queues.
  • Fan-out queue and workers: a durable queue, so a burst of posts becomes a backlog instead of an outage.
  • Timeline cache: one capped list of post ids per user, in memory, sharded by user id.
  • Timeline service: reads that list, then hydrates it: turns ids into full posts with author names, counts and image links, read from a post cache.
  • Post stream: a topic, as in Publish-Subscribe, that search, trending and notifications consume at their own pace.

The expensive work happens at posting time, where nobody waits for it. That choice is called fan-out on write. Is it always the right one?

Part 3 · Deep Dives

Fan-out on write or on read

Core deep dive

This is the decision the rest of the design hangs on: where the work of building a timeline happens, and who waits while it runs.

Think of a newspaper. Home delivery does the work before anyone wakes up, once per subscriber. A newsstand does nothing until a reader walks over and collects each paper. Timelines can be built either way.

Fan-out on write, also called push, copies each new post id into every follower’s timeline when the post is written. Fan-out on read, or pull, does nothing at posting time: when Ravi opens the app, it fetches the recent posts of all 300 accounts he follows and merges them. Set side by side, the two fail in opposite places.

Fan-out on write (push)Fan-out on read (pull)
Work per postOne insert per follower: 200 on average, 30 M for KabirOne write
Work per timeline loadRead one ready-made listFetch recent posts from about 300 accounts and merge
Read latencyLow and steadyGrows with the number of accounts followed
FreshnessA few seconds behind while fan-out runsAlways current
Wasted workLists built for people who never open the appNone
Breaks onAccounts with millions of followersRead traffic: 23,000 loads a second × 300 ≈ 7 M lookups a second

Push breaks on huge accounts; pull breaks on read traffic. The daily totals settle the default. Push costs 100 M posts × 200 followers = 20 B small inserts, all in the background. Pull costs 2 B loads × 300 accounts = 600 B lookups, each inside a request a person is waiting for. Push does about 30 times less work, and none of it while anyone waits.

Where push breaks

Kabir’s one post is 30 M inserts, as much as the whole system’s average fan-out for over two minutes (30 M ÷ 230,000 a second ≈ 130 s). Even if that one job got 100,000 inserts a second, his last follower would see it 5 minutes late, and every post queued behind it would wait too.

So push is the right default, but not for Kabir. The next page splits the two.

The hybrid: push for most, pull for the famous

Push works for Ravi and fails for Kabir, so treat the two kinds of account differently. Pick a threshold, say 100,000 followers. Posts from accounts below it are pushed as before, so no single post costs more than about 100,000 inserts. Posts from accounts above it, the high-fan-out accounts, are saved but never fanned out. They are pulled when a reader opens the app and merged into the pushed list.

Pulling sounds like the expensive path we just rejected, but here it is cheap. A typical reader follows only a handful of such accounts, and their recent posts are read by millions, so they almost always sit in the post cache. Pulling five of them costs a few milliseconds.

Predict before you watch: Kabir’s post is never pushed. When Ravi opens the app a minute later, does he see it, and where does it come from?
  • Yes. The timeline service reads Kabir’s recent posts from the post cache and merges them with Ravi’s pushed list.
Animated scenePush for most, pull for the famous0:26

Ravi posts. It is saved, and one fan-out job joins the queue

Read it as steps

Watch for the queue badge when Kabir posts: pushed, his post buries everyone else’s; skipped and merged at read time, it costs Ravi one cache read.

Pushing Kabir’s post would have queued 30 M inserts ahead of every other post; skipping it costs each of his readers one extra cache read. The hybrid moves the work for a few huge accounts from posting time to reading time, where caching makes it nearly free because millions of readers share the same few posts.

Merging at read time

The merge is where the two halves meet, so it must be fast and stay correct when they overlap. Here is the whole read path in a few lines.

python
def home_timeline(user_id, limit=20, before=None):
    ids = timeline_cache.range(user_id, before=before, count=limit)  # pushed ids
    for star in following_high_fanout(user_id):                      # usually 0 to 10
        ids += user_posts.recent(star, before=before, count=limit)   # hot in cache
    ids = sorted(set(ids), reverse=True)[:limit]                     # newest first
    return hydrate(ids)       # full posts; drops deleted posts and blocked authors

Time-ordered ids make the merge a sort of a few hundred integers, and set() removes duplicates.

One load of Ravi’s timeline, with 295 accounts pushed and 5 pulled, breaks down like this.

StepWorkTime, roughly
Read his pushed list1 cache read2 ms
Read the 5 big accounts’ recent posts5 cache reads in parallel3 ms
Merge and keep the newest 20Sort a few hundred idsunder 1 ms
Hydrate 20 posts with authors and countsOne batched read of 20 keys10 ms
Total on the serverabout 16 ms

The load uses under a tenth of the 200 ms budget. Three races need care:

  • Crossing the threshold. An account that goes viral can pass 100,000 followers in a day. Re-check the flag every few hours, not on every follow; a post both pushed and pulled during the switch still shows once, because the merge removes duplicate ids.
  • The author’s own post. Fan-out is asynchronous, so the post service writes the id into the author’s list before replying, or Ravi thinks his post failed and posts again.
  • Late jobs. A delayed job can deliver a 10:00:01 post after a 10:00:05 one. The list is kept sorted by id, so the late id lands in its place.

The threshold is a dial, not a constant: set it so a typical reader merges no more than about 10 pulled accounts.

Fan-out jobs: chunks and backlogs

Below the threshold, push stays the default, and the jobs themselves need shaping. One post can still mean up to 100,000 inserts. As a single job, that keeps one worker busy for about five seconds at 20,000 inserts a second. If the worker crashes near the end, the whole job runs again, and every post queued behind it waits.

So split big jobs into chunks. The first job for a post reads the author’s follower list a page at a time and queues one chunk job per 1,000 followers, each naming the post id and its slice of the list. A 90,000-follower post becomes 90 chunks that many workers run at once, finishing in well under a second. A crash repeats one chunk, and that is harmless: inserting an id that a sorted list already holds changes nothing. Ravi’s 200-follower post stays a single job.

Chunks make a post faster, not cheaper

Kabir’s post, pushed, would still be 30,000 chunks and 30 M inserts. That is why the threshold exists. Accounts just below it, with tens of thousands of followers, also get their own queue, so their chunks cannot delay ordinary posts.

Two habits cut the work further:

  • Skip inactive users. Do not push to accounts that have not opened the app in 30 days. When one returns, build the timeline once with fan-out on read, then put them back on the push list.
  • Push to the recently active first. During a backlog, order chunks so people who opened the app in the last hour get the post first; the rest can wait a minute.

Size the tier from the estimate scene: at about 20,000 batched inserts a second per worker, the 230,000 per second average needs about 12 workers and the final’s 1.2 M peak about 60. Scale between the two with the queue depth, and let the queue absorb anything above 60 as a few seconds of lag rather than errors. Every insert lands in the timeline cache, which the next page sizes.

The timeline cache: sizing

Every insert from the workers lands in the timeline cache, so it must hold every active user’s list and take a million writes a second at the peak. How big is it?

Each user’s timeline is a list of post ids, newest first, capped at 800 entries. A push is two cheap steps: insert the id, then trim back to 800. If someone scrolls past the end, the timeline service falls back to fan-out on read for older pages.

The raw ids are small: 300 M monthly actives × 800 × 8 bytes ≈ 1.9 TB. How they are stored decides the real size. A compact packed encoding roughly doubles it with keys and slack, to about 4 TB. A general-purpose sorted structure with pointers per entry can cost 5 to 10 times the raw bytes, which would mean 10 to 20 TB, so the encoding is a cost decision, not a detail. The choices below assume the compact one.

ChoiceValueWhy
What is storedPost ids only, 8 bytes eachPosts change (likes, deletes); ids never do
Entries per user800About 40 screens of 20 posts
Who gets a listUsers active in the last 30 daysNo memory spent on people who left
ShardingBy user id, with consistent hashingEven spread; adding a node moves few users
CopiesTwo copies of each shardA node failure does not trigger mass rebuilds
Servers4 TB ÷ 64 GB ≈ 60, about 120 with the second copyMemory sets the count, not traffic

Memory, not traffic, sets the server count. At the peak, each of 60 primaries takes about 20,000 inserts and 2,000 timeline reads a second (1.2 M and 115,000 spread across them), far below the roughly 100,000 operations a second one cache node handles.

Hydration, deletes and misses

The cache holds only ids, so every load turns ids back into posts. That step, hydration, is also where deletes and blocks take effect.

Hydration sends one batched multi-get, a single request for many keys, to the post cache, which holds post objects by id, then does the same for author profiles. A popular post appears in millions of timelines, and Kabir’s is read by millions of people, but each is cached once, so the post cache’s hit ratio is very high. Hot posts also sit in each server’s local memory for a few seconds, so millions of readers do not all hit one cache key.

Storing ids instead of whole posts is what makes changes cheap. If timelines held copies of posts, deleting a post from a 90,000-follower account would mean rewriting 90,000 lists, and every like would change them all again. With ids, a delete marks the post deleted in the post store and the post cache, and hydration drops any id whose post is deleted or whose author you have blocked or muted. The stale id falls off the end of each list as newer posts push it past 800.

An unfollow uses the same filter until the old ids age out. A new follow copies that account’s recent posts into your list in the background, so the next load already includes them.

A miss means rebuild, not failure

If a user’s list is missing (a new user, an evicted entry or a lost cache node), the timeline service rebuilds it with fan-out on read and writes it back. That first load takes about 100 ms instead of 16; later loads are fast again. Rebuilds are rate-limited, so a lost node cannot send a wave of them to the database.

With every part in place, the next question is what breaks first as load grows, and what users notice when it does.

Part 4 · Failures and Trade-offs

Bottlenecks and failures

Each part of the design fails in its own way. The table lists each failure, what users see, and how the design copes.

What goes wrongWhat users noticeHow the design copes
Fan-out queue backs up during the finalPosts reach followers lateAdd workers; push to recently active users first; lower the pull threshold for the evening; timelines lag but keep loading
A timeline cache node diesA slower first load for some usersIts replica takes over; missing lists are rebuilt by fan-out on read, rate-limited to protect the database
One post is read by millionsNothing, if handledThe post sits on several cache replicas and in each server’s local memory for a few seconds; images come from the CDN
Kabir’s follower list is hugeSlow follower pages for that accountHe is on the pull side anyway; split follower lists into buckets so no partition holds 30 M rows
The search indexer falls behindNew posts missing from search for a whileIt is a separate consumer of the post stream; it catches up without slowing posting
A retried request posts twiceA duplicate postIdempotency key on POST /v1/posts
A zone or a whole region failsA zone: almost nothing. A region: slower first loads, and a few seconds of posts arrive lateEvery shard keeps a replica in another zone. If a region fails, its users move to another region, which rebuilds timelines on demand at a capped rate; posts not yet copied across appear when the region returns

In almost every row the timeline keeps loading: failures show up as lateness or a slower first load, rarely as errors. That is the eventual consistency chosen in the requirements, paying off.

Trade-offs, monitoring and cost

Every choice in this design bought something and paid for it. Push spends memory and background work to make reads cheap, and accepts a few seconds of staleness. The hybrid adds a second way to build a timeline, more code and more tests, in exchange for never melting on a post like Kabir’s.

Running in two regions adds one more trade. Each region keeps its own timeline caches and fan-out workers, and posts are copied between regions asynchronously, so a post written in Mumbai reaches a follower served from another region a second or two later. Copying synchronously would make every post wait for a cross-region round trip, about 200 ms to another continent, to buy freshness nobody asked for.

Three signals show whether the design is healthy:

Image egress from the CDN dominates the bill. One post in ten carries an image, so 23,000 loads a second × 20 posts pull about 46,000 images a second: at 100 KB for a feed-size copy, roughly 12 PB a month, about $600,000 at $0.05 per GB (check current prices). Memory for about 120 cache servers comes next. Post text, at 37 TB a year, is cheap.

Part 5 · Going further

Search, trending and counts

Optional: three features that grow from the post stream once timelines work. None of them sits on the posting path, so if one falls behind, posting and timelines carry on unaffected.

Search. An indexer reads new posts from the stream, splits the text into terms and adds the post id to each term’s list in an inverted index, as in Distributed Search. People mostly search for what is happening now, so the index is split by time: a small, hot index for the last few days and larger, colder ones behind it. A query reads the hot index first and goes further back only when it needs more results.

Trending. Count hashtags and phrases in short windows, say 5 minutes, per region. A topic trends when its count is far above its usual count for that hour, not merely when it is large. A greeting hashtag that is big every morning is not news; a player’s name jumping from 200 to 40,000 mentions in the last over is. The pipeline has five steps:

A simple trending pipeline
  1. 1Read posts from the streamskip known spam accounts and copy-pasted text
  2. 2Pull out candidate termshashtags, names, short phrases
  3. 3Count per 5-minute window and regionapproximate counters such as a count-min sketch, which trades a little accuracy for fixed memory
  4. 4Compare with the usual count for that hourthe ratio, not the raw count, decides
  5. 5Publish the top 30 per region every minuteapps read a small cached list

Likes and reposts. Kabir’s post can collect thousands of likes a second, all aimed at one number. Use Sharded Counters: each like increments one of N shard counters, and readers see a cached sum refreshed every few seconds. Store the like itself as a row keyed by post and user, and add to the count only when that row is new, so liking twice changes nothing. A repost is a small post that points to the original, so it fans out like any other post and hydration shows the original inside it.

Part 6 · Interview kit

The 45-minute answer

Here is the whole answer on the interview clock: about 40 minutes of design once introductions are done.

MinutesWhat you say and drawKey number or decision
0–5Requirements: post, follow, home timeline newest first, likes. Out: DMs, ads, ranking, edits. Timelines may lag seconds; authors see their own posts at once.Reads beat writes about 20 to 1; p99 under 200 ms
5–8Only the numbers that decide something: posts a second, then the fan-out multiplier.1,160 posts/s × 200 followers ≈ 230,000 inserts/s; 1.2 M at peak
8–13POST /posts with an idempotency key; GET /timeline with a cursor. Posts, follows stored both ways, timeline lists of ids.Time-ordered ids, hash-partitioned
13–22Post service with an outbox, fan-out queue and workers, follower graph, timeline cache, timeline service, post cache. Walk one post and one load.Fan-out on write; a load is one cache read plus hydration
22–37The 30 M-follower account: threshold, merge at read time, chunked jobs. Then the cache: ids only, 800 per user, deletes at hydration.Pull above 100,000 followers; chunks of 1,000; about 120 cache nodes
37–40A cache node and a region failing, what you monitor, what you would add next.Alert on fan-out lag

Pick the deep dive from the interviewer’s questions. Famous accounts, write load or late posts: take the hybrid and chunked jobs. Latency, memory or deletes: take the timeline cache and hydration. Search or trending: spend five minutes on the post stream consumers, then come back.

Say
"Reads outnumber writes about 20 to 1, so I precompute timelines with fan-out on write and accept a few seconds of lag."
Say
"Accounts above 100,000 followers are pulled at read time and merged, so one post never becomes 30 M writes."
Say
"Timelines hold 8-byte ids, not posts, so a delete changes one row, not millions of lists."
Avoid
"Each timeline is a join of follows and posts." At 23,000 loads a second, each merging about 300 accounts, the database melts.
Avoid
"Every post fans out the same way." One 30 M-follower post becomes 30 M inserts and delays everyone else’s posts by minutes.

Interview drill

Interviewers often change the problem halfway to see whether the design bends or breaks. Answer each one before opening it.

The final ends and posting jumps to 10× normal, about 11,600 posts a second, for 20 minutes. What breaks first, and what do you change?
  • Fan-out: about 2.3 M inserts a second against workers sized for 1.2 M. The queue holds the excess, so posts arrive late, not lost.
  • Add workers: 120 cache nodes take about 12 M operations a second, so workers, not the cache, are the limit.
  • Push to recently active users first, and lower the pull threshold to 10,000 followers for the evening.
Product adds Lists: a user groups up to 5,000 accounts and reads them as a separate timeline. What changes?
  • New tables lists and list_members, stored both ways: list to members, and member to the lists holding them.
  • A list is another timeline, so push or pull returns. Most lists are read rarely: build them on read and cache the result for a minute.
  • Push only into lists read daily; big accounts stay on the pull side, and the merge removes duplicates.
Finance wants the timeline cache bill halved, with p99 still under 200 ms. What do you change?
  • Keep 400 ids per user, not 800: 20 screens still come from memory; deeper scrolls fall back to pull.
  • Cache only users active this week; a returning user pays one rebuild of about 100 ms.
  • Keep the second copy, or a lost node sends a wave of rebuilds to the database. Watch the miss rate after each change.
Common miss
Treating the 100,000 threshold as fixed. It is a dial: lower it during a spike, and set it from how many big accounts a typical reader follows.
Common miss
Forgetting the author’s own post. Fan-out is asynchronous, so write the id into the author’s list first, or they think the post failed and post it again.

Wrap-up

Use this page to revise before a mock round.

Next, Design a News Feed keeps this fan-out machinery but replaces newest-first with ranking: candidate pools, scoring signals and cursors into a frozen list.

Part 7 · Check yourself

Quick check

Q1A teammate’s prototype builds each home timeline with a join of follows and posts. At 20,000 loads a second, p99 passes 2 seconds and database CPU sits at 100%. What is wrong, and what is the fix?

Think it through, then check.

Q2Users may now edit a post within 30 minutes of publishing. What changes?

Think it through, then check.

Q3Your pull threshold is 100,000 followers. What do you gain and lose by lowering it to 10,000, or raising it to 1 M?

Think it through, then check.

Q4A fan-out worker crashes after pushing a post id into 600 of the 1,000 timelines in its chunk. What happens next, and why is it safe?

Think it through, then check.

Q5Which building block sits between saving Ravi’s post and updating his followers’ timelines, and why not call the fan-out workers directly?

Think it through, then check.

Next chapter · 29 of 39Design a News Feed~38 min · 2 animated scenes