Sweaty Imposter

Design Twitter / News Feed

System DesignRead HeavyFan-OutMid3 min readUpdated

Every timeline read has to feel instant for 200M daily users. The interview turns on where you pay for fan-out: when a tweet is written, or when a timeline is read.

1 · Requirements

The Prompt

Design a Twitter-like timeline: 500M users, average 200 follows, p95 timeline read < 200ms.

Functional

  • Post a tweet with text and optional media
  • Read a home timeline of followed accounts, newest first, with paging
  • Follow and unfollow accounts
  • Search tweets by keyword

Non-Functional

  • Timeline reads return in under 200 ms at p95
  • New tweets reach followers' timelines within a few seconds
  • Accounts with millions of followers never stall the write path

2 · Estimates

Drill This Phase First

Show the Answer and Estimates

Answer

About 920,000 a second

400M tweets a day is about 4,600 a second. Each one fans out to 200 timelines, so 4,600 times 200 is about 920,000 inserts a second.

Writes
200M daily users posting 2 tweets a day: 400M tweets, about 4,600 a second and 12,000 at peak
Reads
50 timeline loads per user a day: 10B reads, about 115,000 a second and 290,000 at peak
Fan-Out
200 followers per tweet on average: about 920,000 timeline inserts a second on the push path
Storage
About 1 KB a tweet: roughly 150 TB a year in a wide-column store
Cache
800 tweet IDs per timeline at 16 bytes each: about 2.5 TB across a Redis cluster
Celebrities
Accounts over about 10,000 followers, roughly 0.1% of users, get pulled at read time to cap fan-out

3 · API

POST /tweets { text, media_ids? } → { tweet_id, created_at }
GET /timeline?cursor=… → { tweets[], next_cursor }
POST /follow { target_user_id } → 204
GET /users/:id/tweets?cursor=… → { tweets[], next_cursor }
GET /search?q=… → { tweets[] } (search-index path)

Data Model

tweets (wide-column, partition = user_id, cluster = tweet_id DESC):
  tweet_id (PK)  : snowflake
  user_id        : uuid
  text           : string (<=280)
  media_ids      : list<uuid>
  created_at     : timestamp

timelines (wide-column, partition = user_id, cluster = ts DESC):
  user_id (PK)   : uuid
  ts             : timestamp
  tweet_id       : snowflake
  author_id      : uuid

follows (KV, partition = follower_id):
  follower_id    : uuid
  followee_id    : uuid
  created_at     : timestamp

4 · High-Level Design

Writes land in Kafka and fan out to each follower's cached timeline. Reads take the cached timeline from Redis, merge in tweets from followed celebrities, and fall back to Cassandra on a miss.

5 · Deep Dives

Push, Pull, or Hybrid

Push writes each tweet into every follower's cached timeline, so a read is one Redis lookup. That breaks for accounts with millions of followers, where one tweet means millions of writes. The hybrid pushes for normal accounts and pulls celebrity tweets at read time, merging the two lists before returning the page.

Timeline Cache Shape

Store only tweet IDs, capped at about 800 per user, and hydrate the tweet bodies in one batch read. Inactive users can fall out of the cache and get a rebuilt timeline on their next visit, which keeps the 2.5 TB estimate from growing with total signups.

Cursor Paging

Page with a cursor built from the last tweet ID. Snowflake IDs sort by time, so the cursor stays stable while new tweets arrive. Offset paging skips or repeats tweets whenever the timeline grows between page loads.

6 · Wrap Up

Close on the risks you would watch next: hot partitions for celebrity accounts, fan-out lag during traffic spikes, and ranking once the timeline stops being purely chronological.

The Short Version

Fan-out-on-write for normal users (push to followers' timeline cache), fan-out-on-read for celebrities (pull at read). Wide-column store for tweets.