Design exercise: news feed
Generated by Claude (Fable 5.1) by hand, Scout learning center M4a on 2026-10-12, reviewed by a second reader (Claude Fable 5.1), Scout learning center M4b on 2026-10-12.
Concept: System design and architecture · also covers Scaling, Relational and non-relational data stores, Distributed systems · Part C (Design exercises) of the system design module · design exercise · version 2
On this page
- The prompt
- Constraints and numbers
- Clarifying questions worth asking
- Reference answer
- Requirements and estimates
- High-level design
- The data model
- The core flow, step by step
- Scaling and the bottleneck
- Failure modes and what degrades
- Trade-offs and the alternative not taken
- What changes at 10x
- Self-check rubric
- The 45-minute interview frame
- The design-doc frame
- Depth notes
- Sources
- Related lessons
A classic design exercise: the home feed of a social product with a heavy-tailed follower graph. The reference answer sizes fan-out from stated assumptions, chooses hybrid fan-out (on write for most authors, on read for celebrities), keeps a bounded timeline cache of post ids per user, ranks at read time behind a deadline, and treats fan-out lag as the freshness SLI.
Attempt the design from the prompt, the constraints and the questions before reading the reference answer.
The prompt
Design the home feed of a social product: a user follows other users and sees their recent posts, newest or best first, every time they open the app. Posting must feel instant and the feed must open fast even for people who follow thousands of accounts.
Constraints and numbers
Functional
Create a post (text, optional media reference); follow and unfollow; read the home feed with paging; a post appears in followers' feeds within seconds; an unfollow removes the author's future posts from the feed.
Scale (assumption)
50 million registered users, 10 million daily active (assumption). Each user follows 200 accounts on average; a few thousand accounts have more than 1 million followers. 2 million posts per day; each active user opens the feed 10 times a day. Peak rates are 10x the daily average. Derive the per-second rates, the fan-out volume and the timeline cache size yourself.
Targets (assumption)
Feed open p99 under 200 ms from the service; a post visible to followers within 5 s at p99; 99.9 percent availability for reads; posting may be less available than reading. A feed holds the latest 800 post ids per user (assumption; the public Twitter talk describes a bounded in-memory timeline per user).
Non-goals
Search, direct messages, ads, media transcoding, the ranking model itself (treated as a service with a known latency).
Clarifying questions worth asking
- Chronological or ranked? Ranked, but the candidate set is chronological: the last few hundred posts from followed accounts, re-ordered by a ranking service at read time.
- How fresh must the feed be? A new post should be visible within 5 s; a few seconds of staleness in the ranked order is fine.
- What is the follower distribution? Heavy-tailed: a median user has a few hundred followers; a few thousand accounts have millions.
- Do deletes and unfollows have to be immediate? A deleted post must never render; it may stay in cached id lists and be filtered at read time. Unfollow stops future posts; past ones may linger until they age out.
- Is the feed read when the user is offline? No; feeds are computed for active users only. A user inactive for 30 days (assumption) has no cached timeline and gets one rebuilt on return.
- Can a reader see a post the author later restricts? Visibility is checked at render time against the current setting, never baked into the cache.
Reference answer
Requirements and estimates
Derived numbers: writes and reads
Posts: 2 million per day is about 23 per second average, 230 at peak. Feed reads: 10 million users times 10 opens is 100 million per day, about 1,160 per second average and 11,600 at peak. Fan-out on write: each user follows 200 accounts, and in a closed graph every follow edge has one follower and one followee, so the average follower count equals the average followee count, 200; 2 million posts times 200 followers is 400 million timeline inserts per day, 4,600 per second average and 46,000 at peak, each an append of a 16-byte entry (post id plus timestamp) to a list. The write amplification of 200 is the price of a cheap read.
Derived numbers: storage and cache
Timeline cache: 800 entries times 16 bytes is 12.8 kB per user; 10 million active users is 128 GB, a small cluster of memory nodes. Post content: 2 million posts times 1 kB is 2 GB per day, 730 GB per year in the post store before media; a content cache of the last two days (4 GB) serves nearly all feed renders.
The latency argument
Reading a feed from the cache is one memory lookup of a few hundred ids (main memory reference 100 ns, one datacenter round trip 500 us, both from the latency reference numbers) plus a batched fetch of post content: well inside 200 ms. Assembling a feed at read time from 200 followed accounts instead means 200 lookups whose slowest one sets the latency; the tail-at-scale argument says the p99 of a fan-out is dominated by its worst member, which is why the answer precomputes.
Cost per daily active user (assumptions)
Per active user per day the design spends 25.6 kB of memory (the 12.8 kB timeline and one replica), 40 timeline appends (400 million appends divided by 10 million active users) and 10 feed reads with their content fetches. At an assumed 10 dollars per GB-month for replicated memory and 1 dollar per million appends including the fan-out workers' compute (both assumptions, chosen to make the arithmetic concrete), memory is about 0.0003 dollars per user per month and fan-out about 0.0012, so fan-out compute, not the cache, is the larger line and the celebrity threshold is the lever on it. That sum per daily active user per month is the number the team is held to, and it should be set before the design, not read off the bill afterwards.
High-level design
Container view
A post is stored once and its id is pushed, asynchronously, into the timeline cache of every follower (fan-out on write). The feed service reads the user's cached ids, merges in recent posts from the few celebrity accounts the user follows (fan-out on read for those only), asks the ranking service to order the candidates, and renders content from a post cache. The primer's Twitter worked solution describes this shape in text: a fan-out service that stores each tweet in the home timeline of every follower in a memory cache, several hundred entries kept per timeline, a timeline rebuilt for a user inactive for 30 days, and the advice to avoid fanning out tweets from highly followed users and merge them at serve time instead; the public Twitter talk is the first-hand account of the same hybrid (further viewing); LinkedIn's FollowFeed describes the opposite default, fan-out on read over a timeline index, which this answer uses only for celebrities.
flowchart TB
subgraph Write path
A[Authors] --> PS[Post service]
PS --> PDB[(Posts, sharded by post id)]
PS --> Q[[post events]]
Q --> FO[Fan-out workers]
FO --> G[(Social graph: follows)]
FO --> TL[(Timeline cache: post ids per user)]
end
subgraph Read path
R[Readers] --> FS[Feed service]
FS --> TL
FS -->|celebrity posts| PDB
FS --> RK[Ranking service]
FS --> PC[(Post content cache)]
endflowchart TB
subgraph Write path
A[Authors] --> PS[Post service]
PS --> PDB[(Posts, sharded by post id)]
PS --> Q[[post events]]
Q --> FO[Fan-out workers]
FO --> G[(Social graph: follows)]
FO --> TL[(Timeline cache: post ids per user)]
end
subgraph Read path
R[Readers] --> FS[Feed service]
FS --> TL
FS -->|celebrity posts| PDB
FS --> RK[Ranking service]
FS --> PC[(Post content cache)]
endBoxes are containers; the two frames are the write and read paths; cylinders are stores; double brackets are logs.
The data model
Entities
Post {post_id (time-ordered 64-bit, Snowflake layout), author_id, created_at, text, media_ref, visibility, deleted}. Follow {follower_id, followee_id, created_at} indexed both ways: followers of X for fan-out, followees of Y for celebrity merge. Timeline {user_id -> list of (post_id, created_at), capped at 800, newest first}. The social graph is the read-hottest table in the system; the TAO paper describes a read-optimised, cached graph store for exactly this shape of workload.
API
POST /posts {text, media_ref?} returns {post_id}; POST /follows/{followee}; DELETE /follows/{followee}; GET /feed?cursor=&limit=50 returns {items[], next_cursor}. The cursor is the smallest post id on the page: time-ordered ids make paging stable while new posts arrive at the top.
The core flow, step by step
Posting and reading, in the order a request sees them.
- Post: the post service writes the post to the post store and emits a post event; it returns as soon as the write is durable, so posting feels instant regardless of follower count.
- Fan-out: a worker consumes the event, reads the author's follower list in pages of 10,000, and appends the post id to each follower's timeline list, trimming to 800. For an author above the celebrity threshold (1 million followers, assumption) the worker skips fan-out and marks the post as celebrity-only.
- Read: the feed service fetches the user's timeline ids from the cache (a miss means a rebuild from the followees' recent posts, bounded to the last 48 hours).
- Celebrity merge: for the few celebrities the user follows (cached per user), fetch their recent post ids from the post store and merge by time.
- Rank: send the candidate ids with features to the ranking service with a deadline of 50 ms (assumption); on timeout, fall back to chronological order and mark the response as unranked.
- Render: batch-fetch content from the post cache, drop deleted or now-restricted posts, page by cursor, return.
Scaling and the bottleneck
Fan-out volume and the celebrity threshold
The bottleneck is fan-out write volume: 46,000 appends per second at peak, bursting to millions when a popular author posts. The timeline cache is partitioned by user id across memory nodes; appends are batched per target node, so one post to 10,000 followers becomes a few dozen batched calls, not 10,000. Fan-out lag (post event time to last append) is the freshness SLI; the 5 s target bounds the queue depth, and workers scale on lag, not on CPU. The celebrity exception keeps the worst case out of the write path entirely: without it, one post by an account with 10 million followers is 10 million appends, or about 3.6 minutes of the whole fleet's peak capacity.
Keeping reads cheap and backfill lazy
Reads are cheap by construction (one cached list plus one batched content fetch), so the read path scales by adding feed service replicas and cache replicas. The ranking call is the only expensive step; it is bounded by a deadline and degrades to chronological. Backfill for new followers is lazy: on follow, the followee's last 48 hours of post ids are merged into the follower's timeline on the next read, not pushed at follow time, so a wave of follows after a viral moment does not become a write storm.
Failure modes and what degrades
Each row names the failure, what the reader sees, and the mitigation.
| Failure | Effect | Mitigation |
|---|---|---|
| Timeline cache node lost | feeds on that shard rebuild from the graph and post store | replicate timelines to a second node; rebuild bounded to 48 hours; rate-limit rebuilds |
| Fan-out lag grows past 5 s | new posts appear late | lag-based autoscaling of workers; alert on lag; posts still visible via rebuild |
| Ranking service slow or down | chronological feed | deadline of 50 ms then fallback; response marked unranked |
| Post store primary down | no new posts; feeds still render from cache | replicas serve reads; posting returns a clear error |
| Viral post by a non-celebrity | a burst of up to a million appends from one post | batched appends per node; a dynamic celebrity threshold on follower count at post time |
| Deleted post still in cached lists | would render stale content | filter against the post record at render time; tombstone in the content cache |
Trade-offs and the alternative not taken
The decisions a reviewer reads first.
| Decision | Options | Chosen | Why |
|---|---|---|---|
| Fan-out strategy | on write for all, on read for all, hybrid | hybrid: write for most, read for celebrities | cheap p99 reads for the 99 percent; the celebrity exception caps write amplification |
| Timeline storage | memory cache of ids versus durable per-user tables | memory cache of ids, rebuildable | a derived view; losing it costs a rebuild, not data |
| Ranking placement | rank at fan-out time versus at read time | at read time with a deadline | ranking changes faster than posts; stale scores would need re-fan-out |
| Backfill on follow | push at follow time versus lazy merge at read | lazy merge | follows are bursty after viral moments; the read path already merges |
What changes at 10x
At 100 million daily active users the timeline cache is 1.3 TB and fan-out peaks near 500,000 appends per second: the cache becomes cells of users (the cells lesson) so a cell failure costs 1 percent of feeds (100 cells, assumption), not all; fan-out workers are partitioned by target cell. Ranking moves to a two-stage design (cheap pre-filter in the feed service, heavy model on the top 200). The celebrity threshold drops, and more of the feed is assembled at read time from per-author recent-post lists, moving the design toward the FollowFeed shape. A feed freshness SLO and a cost per daily active user become the numbers the team is held to.
Self-check rubric
Read, not scored: find the level your answer reached.
L1, mid-level: applies
- Fan-out on write with a bounded timeline cache per user, and the celebrity exception stated with a threshold.
- A post store separate from the timeline lists, with posts written once and referenced by id.
- Cursor paging on time-ordered ids, and the fan-out arithmetic (400 million appends per day from the stated assumptions).
L2, senior: chooses with numbers and defends
- Ranking as a separate service with a deadline and a chronological fallback.
- Timeline cache sizing (128 GB) and the lazy backfill for new followers.
- Fan-out lag as the freshness SLI and the scaling signal, with batched appends per cache node.
- The tail-at-scale argument for why read-time assembly from 200 accounts misses the p99 target.
L3, staff: sets the constraint and judges
- The feed freshness SLO and a cost per daily active user as the constraints set before the design.
- Cells for the timeline cache and the move toward read-time assembly as the follower graph grows.
- What the cache must never decide: visibility and deletion are checked at render time, not at fan-out time.
The 45-minute interview frame
- 0:00 to 0:05 Requirements: ask about ranking, freshness, the follower distribution, deletes and offline users. Say the numbers aloud: 230 posts per second at peak, 11,600 feed reads per second at peak, 46,000 timeline appends per second at peak, a 128 GB timeline cache.
- 0:05 to 0:10 Entities and API: Post, Follow (indexed both ways), Timeline; the four endpoints; cursor paging on time-ordered ids.
- 0:10 to 0:20 High-level design with the diagram: write path (post store, event, fan-out workers, timeline cache) and read path (feed service, celebrity merge, ranking, content cache). Name the hybrid fan-out as the central decision.
- 0:20 to 0:40 Two deep dives: fan-out volume and the celebrity threshold (batched appends, lag-based scaling, the 10-million-follower arithmetic), then the read path (one cached list, the ranking deadline and fallback, lazy backfill, render-time visibility checks).
- 0:40 to 0:45 Failure modes from the table, the 10x plan (cells, two-stage ranking, a lower celebrity threshold), and the L3 sentence: the timeline cache is a derived view; the SLO that matters is fan-out lag, and the cost that matters is per daily active user.
The design-doc frame
- Context and scope. The product's follow graph, the current feed implementation if any, the ranking service's contract, and the freshness complaints or cost that motivate the design.
- Goals and non-goals. Feed open p99 of 200 ms, visibility within 5 s, read availability of 99.9 percent; search, messaging, media and the ranking model are out of scope.
- Design. This answer fills it: the hybrid fan-out, the timeline cache, the read path with ranking deadline, the data model and cursor paging.
- Alternatives considered. Fan-out on read for everyone (FollowFeed shape), ranking at fan-out time, push-at-follow backfill, with the trade-off table.
- Cross-cutting concerns. Observability (fan-out lag, cache hit ratio, unranked response rate), privacy (visibility checked at render), cost per daily active user, rollout by user cohort with the old feed as fallback.
- Review questions the doc must answer. What happens when an account crosses the celebrity threshold mid-day, and how a deleted or restricted post is kept out of every reader's feed.
Depth notes
- L1. Fan-out on write with a timeline cache and the celebrity exception.
- L2. Ranking as a separate service, cache sizing, backfill for new followers, pagination semantics.
- L3. The hybrid fan-out, feed freshness SLO, cost per daily active user.
Sources
- talkInfoQ: Timelines at Scale (Raffi Krikorian; video, further viewing) (www.infoq.com)
- blogLinkedIn Engineering: FollowFeed, LinkedIn's feed made faster and smarter (www.linkedin.com)
- paperTAO: Facebook's distributed data store for the social graph (USENIX ATC 2013) (www.usenix.org)
- repoSystem Design Primer: Twitter timeline worked solution (github.com)
- docLatency numbers every programmer should know (gist.github.com)
- paperThe Tail at Scale (Dean and Barroso) (research.google)