Back to System design

Design Twitter / News Feed

hard
Scale: 5K tweets/s sustained, 60K/s peak; 10M timeline reads/s sustained, 100M/s peak Storage: ~500B/tweet × 500M/day = 250GB/day raw; timeline cache ~2.5 TB Twitter, Meta, LinkedIn
Case StudyFeedFanout

When you open the app, you see posts from everyone you follow, newest first. The question is when that list gets built.

Scale5K tweets/s sustained, 60K/s peak; 10M timeline reads/s sustained, 100M/s peak
Storage~500B/tweet × 500M/day = 250GB/day raw; timeline cache ~2.5 TB

Key Concepts

1
There are two answers, and the whole design follows from which you pick.
2
Build it when someone posts. As soon as I post, my post is added to a ready-made list for each of my followers. When you open the app, your list is already sitting there. Reading is one quick lookup.
Build it when someone posts.
3
Build it when someone reads. Store each post once. When you open the app, fetch recent posts from everyone you follow and merge them on the spot. Posting is cheap; reading does all the work.
Build it when someone reads.
4
The number that decides it. Say 200 million people use the app daily and post twice a day. That is 400 million posts, or about 4,600 per second.
The number that decides it.
5
Now multiply by followers. With 200 followers on average, one post becomes 200 list updates. That is 920,000 list updates per second, and several million at busy times.
920,000 list updates per second
6
That is a lot, but it is manageable for a simple key-value store. So building at post time is usually the right default.
7
Until a celebrity posts. Someone with 50 million followers turns one post into 50 million updates. At 100,000 updates per second per machine, that is minutes of work. Some followers see the post long after others do, and celebrities post at peak times.
Until a celebrity posts.
8
So real systems do both. Ordinary accounts are fanned out at post time. For the few thousand huge accounts, nothing is fanned out; their recent posts are fetched when you open the app and merged into your ready-made list.
9
Where you draw the line between "ordinary" and "huge" is a number you tune, not a constant. Say that, and say you would work it out from the follower distribution.
10
Store post IDs, not posts. Each user's list holds 800 post IDs, 8 bytes each. For 200 million users that is about 1.3 TB, which fits in memory across a fleet of cache servers. The posts themselves are fetched separately when the list is displayed.
Store post IDs, not posts.
11
This matters for more than memory. When someone deletes a post, you change one row. You do not have to find and edit millions of lists — the lists simply skip an ID that no longer resolves.
12
Ranking comes after this, not before. A feed in time order is a complete answer to the question asked. If ranking comes up, the shape is: gather candidates, score them with a model, then re-rank so one author cannot fill the screen. Do not start there.
Ranking comes after this, not before.
13
What the interviewer is probing.
What the interviewer is probing.
14
1. "Design Twitter's timeline." Probing: whether you find the real decision. Stalls: starting with database tables. Moves up: naming both build-at-post and build-at-read, then the number that chooses between them.
1. "Design Twitter's timeline."
15
2. "Someone with 50 million followers posts." Probing: whether your design has a known worst case. Stalls: "The queue handles it." Moves up: 50 million updates is minutes of lag; treat big accounts differently.
2. "Someone with 50 million followers posts."
16
3. "How much memory do the lists need?" Probing: IDs versus whole posts. Stalls: storing posts per follower. Moves up: 800 IDs x 8 bytes x 200 million users is about 1.3 TB, and IDs make deletes cheap.
3. "How much memory do the lists need?"
17
4. "A user deletes a post that has already been fanned out." Probing: the consequence of your choice. Stalls: "Remove it from every list." Moves up: the lists hold IDs, so a missing post is simply skipped.
4. "A user deletes a post that has already been fanned out."
18
5. "Now rank the feed." Probing: whether you separate finding from ranking. Stalls: jumping to a model. Moves up: candidates, then scoring, then a pass for variety.
5. "Now rank the feed."

High-level design

Write path: client → API → tweet service (write to durable store) → Kafka FanoutEvent → fanout workers → timeline cache.
Read path: client → API → timeline cache (precomputed) + celebrity outbox pulls (merged) → ranker → response.
Search: tweets → Kafka → Elasticsearch indexer.
Media: tweets with photos/videos → S3 + CDN.
Async pipelines: notifications, analytics, recommendation training.

Components

  • Tweet service (write path; sharded by tweet_id).
  • Social graph service (followers, following).
  • Fanout workers (Kafka consumers).
  • Timeline cache (Redis sorted set per user, capped at 800 entries).
  • Durable timeline store (Cassandra wide row) for cold/pagination.
  • Ranker (TF Serving / Triton).
  • Search indexer + Elasticsearch.
  • Media service (S3 + CDN, transcoding pipeline).
  • Notification service.
  • Anti-abuse service (spam, hate, NSFW).

Capacity, worked

  Daily active users                            200,000,000
  Posts per user per day                                  2
  Average followers                                     200
  -----------------------------------------------------------
  Posts per day        200M x 2        =        400,000,000
  Posts per second     400M / 86,400   =              4,630
  At 3x peak                           =             13,900
  Fan-out writes/s     4,630 x 200     =            926,000
  At peak              13,900 x 200    =          2,780,000
  Timeline cache
    Ids held per user                                   800
    Bytes per id                                          8
    200M x 800 x 8 B                   =            1.28 TB
  Post storage
    Body + metadata per post                      ~1,000 B
    400M/day x 365                     =        146B posts/yr
    146B x 1 KB                        =          ~146 TB/yr

Consequences:
- ~1M timeline writes/s is why the timeline store is a key-value fleet,
not a relational database.
- 1.28 TB of ids fits in memory across a Redis cluster; 146 TB of bodies
does not, which is why timelines hold ids and hydrate separately.
- A 50M-follower account is 50M writes for one post: at 100k writes/s per
node that is 500 node-seconds, and the reason for the hybrid path.

Hybrid fan-out, concretely

  Ordinary account (below the threshold)
    Post -> queue -> fan-out workers -> append id to each follower's list.
    Reader: single range read. Latency dominated by one cache hit.
  Large account (above the threshold)
    Post -> stored once, no fan-out.
    Reader: range read of the assembled timeline, plus a fetch of recent
    posts from the handful of large accounts they follow, merged in memory.
  Choosing the threshold
    Not a constant. Derive it: fan-out cost rises linearly with followers,
    merge cost rises with the number of large accounts a reader follows.
    The crossover for most follower distributions lands in the tens of
    thousands. State the method, not a magic number.
  Consequences to mention before you are asked
    - A reader following 50 large accounts pays 50 extra reads; cap it by
      caching each large account's recent posts once, globally.
    - Deletion touches one post row, not millions of lists, because
      timelines hold ids and hydration skips missing posts.

Failure modes in production

Fan-out lag during peak
Symptom: some followers see a post minutes late; engagement skews to
whoever was written first.
Cause: queue depth grows faster than workers drain it.
Fix: priority by recency of follower activity — write to users who are
online now first, backfill the rest.

  Timeline cache cold after a node loss
    Symptom: a slice of users get slow feeds for minutes.
    Cause: lists were only in memory.
    Fix: rebuild on read from the posts store, and treat the cache as
    reconstructible rather than authoritative.
  Unbounded timeline growth
    Symptom: memory climbs steadily for heavy users.
    Cause: appends without trimming.
    Fix: cap at ~800 ids on write; older history comes from the posts store
    on demand.
  Follower-count spike
    Symptom: an account crosses the hybrid threshold mid-day and fan-out
    workers saturate.
    Cause: threshold evaluated only at post time.
    Fix: re-evaluate on follower-count change, and make the switch
    per-account rather than per-post.