Designing Twitter/X — Timeline & Feed System
A simple read-time timeline query works until an account with tens of millions of followers posts, and suddenly every one of those followers' reads is competing for the same hot rows. This walkthrough traces that breaking point into fanout-on-write, the celebrity write-amplification problem it creates, and the hybrid fanout, ranking, and caching that resolve it.
Problem Definition
We are designing the timeline and feed system for a social platform: users post content, follow each other, and expect a personalized, fresh feed to load instantly.
The challenge: reads massively outnumber writes. A post is created once but potentially read millions of times. And the distribution of follower counts is wildly skewed — most users have hundreds of followers, but some users have tens of millions. That asymmetry is what makes this problem hard.
Visual Design Journey
Watch one feed architecture evolve from a naive read-time timeline into hybrid fanout, ranking, caching, and hot-partition mitigation — driven by a single celebrity post that breaks the naive design wide open.
Requirements
Establish the functional and non-functional requirements before touching the architecture.
Functional: create posts, follow/unfollow users, view personalized timeline, like/reply/repost. Non-functional: very high read volume (reads >> writes), low timeline latency, support users with millions of followers, eventual consistency is acceptable for timelines.
Capacity estimate (illustrative)
These are illustrative planning numbers, not measurements of any real system. They exist to show which numbers drive the design. Assumptions: 200 million daily active users (DAU); 100 million new posts per day; each DAU opens the timeline 10 times per day; fanout-eligible authors average 200 followers (accounts above the hybrid threshold are excluded from fanout and handled at read time); peak traffic is 3× the daily average; 86,400 seconds per day.
| Quantity | Formula | Result |
|---|---|---|
| Posts per second | 100M ÷ 86,400 s (×3 for peak) | about 1,160/s average, about 3,500/s peak |
| Timeline reads per second | 200M × 10 ÷ 86,400 s (×3 for peak) | about 23,000/s average, about 69,000/s peak |
| Read-to-write ratio | 2B reads/day ÷ 100M posts/day | about 20 : 1 |
| Inbox writes (fanout) | 100M posts × 200 followers = 20B/day (upper bound: treats every post as fanout-eligible); ÷ 86,400 s (×3 for peak) | about 231,000/s average, about 694,000/s peak |
| One celebrity post, naive fanout | 1 post × 50M followers | 50M inbox writes — about 216 s of the entire fleet's average fanout load, or 50 s even at 1M writes/s |
| Inbox memory | Assumed 500 post IDs per inbox × 32 B per entry (ID plus sorted-set overhead) = 16 KB; × 200M inboxes | about 3.2 TB, roughly 50 nodes at 64 GB each before replication |
| Post metadata growth | Assumed 300 B per post × 100M posts/day | about 30 GB/day (media excluded) |
What this implies for the design: writes are cheap (a few thousand posts per second) but fanout turns each one into hundreds of inbox writes, so the fanout workers and inbox store, not the Posts DB, are the write bottleneck. The 20:1 read ratio is what justifies pre-computing inboxes. A single 50-million-follower post would cost as much as several minutes of normal fanout, which is why the celebrity exclusion in the hybrid design is a requirement rather than an optimisation.
Start Simple
Start with the simplest possible design: a post service, a posts database, and read-time timeline generation.
When a follower opens their timeline, the Timeline Service fetches their following list from the Social Graph, then fetches recent posts from each followed user. Simple to implement. Simple to reason about. Works at small scale.
Where it breaks
If a user follows 1,000 accounts, loading their timeline requires up to 1,000 database queries. Multiply by millions of concurrent users opening their timelines at peak. The Posts DB and Timeline Service saturate.
Fanout-on-Write
The alternative: do the heavy lifting at write time. When a post is created, push it to every follower's personal timeline inbox immediately.
The fanout process is asynchronous. The user who created the post doesn't wait for it. Workers pick jobs off a queue and write the post ID into each follower's inbox — a per-user, pre-built list of recent post IDs.
Timeline inbox
A per-user data structure (typically in Redis) that stores the post IDs of the last N posts from accounts the user follows. Loading the timeline is trivial: fetch the inbox, hydrate the post details from the Posts DB.
Write amplification
The cost of fanout-on-write is O(followers). For a user with 500 followers, one post creates 500 inbox writes. This is manageable. But it grows linearly — and for celebrity users, it becomes catastrophic.
- 01Timeline read cost is bounded and independent of follow count — just fetch the pre-built inbox
- 02Write cost is O(followers per post creator)
- 03Fanout is asynchronous — users don't wait for it
- 04Acceptable for users with small-to-moderate follower counts
- 05Catastrophic for users with millions of followers
The Celebrity Problem
A celebrity with 50 million followers creates one post. Fanout-on-write must now create 50 million inbox writes.
A naive design enqueues one job per follower, so a single post from a 50-million-follower account puts tens of millions of jobs on the fanout queue at once. Workers are overwhelmed. The queue backlog grows. And while the system drains this celebrity post backlog, all other users' posts are stuck in the queue too — their followers see delayed timelines even for normal posts.
A single celebrity post has become a denial-of-service event for the entire fanout system.
Hybrid Fanout
The solution: use different fanout strategies for different users based on their follower count.
Normal users use fanout-on-write. Their posts are pushed to follower inboxes asynchronously. Accounts above a high follower-count threshold are excluded from fanout — their posts sit in the Posts DB and are fetched at read time instead.
Timeline composition
When a follower opens their timeline, the Timeline Composer: (1) fetches their pre-built inbox from normal user fanout, (2) fetches recent posts from celebrity accounts they follow directly from the Posts DB. The number of celebrities a typical user follows is small — so this read-time fetch is bounded. The two sets are merged, ranked, and returned.
Threshold
The cutoff is a tunable follower-count threshold, not a fixed number to claim as fact. Set it from the fanout capacity: with the illustrative numbers in the Requirements chapter (about 231,000 inbox writes per second on average), one account's post must stay a small fraction of the queue and worker budget, and the threshold should sit well below the point where a single post would saturate it. It also depends on the read-time cost of the opposite choice, since every follower of an excluded account pays an extra fetch on each timeline open. Accounts near the boundary can move across it as their follower counts change.
Timeline Ranking
A chronological feed isn't the best experience. A ranking service scores candidate posts to produce a personalized, relevant feed.
The timeline is never ranked from every post in the system — that would be both wasteful and unbounded. Candidate posts are first collected from available sources (the precomputed inbox and any pull sources), then filtered and have their features retrieved, and only then does ranking order that bounded pool: candidate sources → filtering → feature retrieval → ranking → final feed.
Signals include recency, engagement metrics (likes, replies, reposts), relationship strength (how closely connected the reader is to the author), content relevance based on the reader's history, and media type preferences.
Separation of concerns
Ranking is deliberately decoupled from candidate collection. The fanout system collects candidates; the ranking system orders them. This lets both evolve independently — you can ship a new ranking model without touching the fanout infrastructure, and vice versa.
The full multi-stage pipeline — how candidates are actually generated and merged, how features are hydrated, and how scoring stages are structured — is covered in Architecture Deep Dive → Candidate Generation & Feed Composition and → Ranking Pipeline.
Timeline Caching
Active users check their timelines many times a day. Recomputing the full timeline on every request is wasteful — cache it.
The timeline cache (Redis-like) stores the composed, ranked timeline per user. Cache hit: return from memory with no database touch (a networked cache typically answers in about a millisecond or less). Cache miss: run full composition — inbox fetch + celebrity fetch + ranking — store the result, return it.
"Timeline caching" is really shorthand here — a production feed has distinct cache layers for post/object data, timeline/inbox candidates, and the final composed/ranked result, each with different freshness needs. See Architecture Deep Dive → Multi-Layer Feed Caching for how those layers differ.
Cache freshness
A cached timeline introduces staleness: a new post might not appear until the TTL expires or the cache is explicitly invalidated. Given that eventual consistency is acceptable, a short TTL — for illustration, on the order of tens of seconds, as in the Deep Dive — is typically fine. Users accept that their feed isn't real-time to the second.
Hot Partitions
A skewed access pattern concentrates an enormous amount of traffic on one storage partition or key. Adding generic capacity doesn't automatically redistribute a hot key — the load stays concentrated unless the underlying skew itself is addressed.
This shows up as skewed access to a small number of keys or shards rather than evenly spread load: an extremely popular post being read by millions at once, celebrity-related metadata being looked up constantly, a disproportionately active shard, a high-degree social-graph node, or a cache-miss storm concentrating reads onto a subset of partitions right when the cache can least absorb it.
Why more machines don't help
The hot traffic targets a specific set of keys — whichever partition those keys happen to land on stays saturated. Adding more partitions doesn't move existing keys without an explicit resharding operation, so generic horizontal scaling alone doesn't relieve a genuinely hot key; it just adds idle capacity elsewhere.
Mitigations
First line: the cache absorbs the overwhelming majority of reads, so only misses reach the storage layer at all. Second: read replicas on hot partitions spread read load across multiple nodes serving the same shard. Third, for genuinely extreme hot keys: explicit key/data sharding — splitting a single high-traffic key's data across multiple nodes. If load is still unmanageable, load shedding protects the rest of the system at the cost of some requests.
Failure Scenarios
At scale, each component will fail. The architecture must degrade gracefully rather than catastrophically.
Fanout workers fail
New posts are still stored in the Posts DB. The fanout queue grows. Follower timelines become stale — they stop receiving new posts in their inboxes. When workers recover, they drain the backlog. No data is lost. Impact: temporarily stale timelines.
Timeline cache fails
All reads fall through to full composition. The Timeline Composer and ranking service handle elevated load. Timelines are correct but slower. When the cache recovers, it warms up organically as requests come in.
Ranking service unavailable
Fall back to a simpler ordering strategy: reverse chronological. Timeline is still served, just not personalized. This is a graceful degradation — less ideal but fully functional.
Celebrity post
Hybrid fanout prevents write amplification. The celebrity's post lands in the Posts DB. Read-time fetch handles delivery to followers. The fanout system is largely unaffected on the write path.
Core Trade-offs
Every decision in this design involves an explicit trade-off. Name them.
Fanout-on-write
Fast reads. Expensive writes. Problematic for users with very large follower counts. Best for: users with small-to-moderate follower counts where write amplification is bounded.
Fanout-on-read
Cheap writes. Expensive reads. More computation at timeline request time. Best for: celebrity users where write amplification would be catastrophic.
Hybrid
Best of both worlds at the cost of architectural complexity. The threshold between strategies is a tunable parameter. Requires handling transitions when users cross the threshold.
Chronological vs ranked feed
Chronological is simple, predictable, and transparent to users. Ranked improves relevance and engagement but introduces algorithmic opacity and requires significant ML infrastructure.
Feed Correctness Beyond New Posts
So far the story has mostly modeled create post → distribute post → read feed. That's not the whole picture — feed entries can become invalid after fanout has already happened.
A post already sitting in a follower's inbox can stop being valid to serve for several reasons: the post is deleted, the author blocks the reader (or the reader blocks the author), the account is suspended or made private, the reader unfollows the author, a repost is removed, or visibility changes in some other way. None of these are exotic edge cases — at scale, some combination of them is happening constantly.
The cleanup algorithms and full serving-time mechanics live in Architecture Deep Dive → Post Lifecycle, Deletion & Visibility — this section exists just to establish that the problem is real before the final architecture is assembled.
Final Architecture
The complete architecture, assembled from first principles.
Write path: User posts → Post Service → Posts DB (source of truth). Fanout job enqueued. Workers consult the Social Graph for follower lookup: normal-strategy users → inbox writes; pull-strategy users → skipped. Read path: Timeline request → cache → candidate collection (inbox + pull sources) → visibility/filtering → feature hydration → ranking → final feed. The Social Graph is also consulted directly on the read path wherever pull-source discovery needs a reader's followed-account list, not only during fanout.