◐ system-design/design-twitter.md
33. Design Twitter / News Feed
The canonical fan-out problem. Tests: timeline construction, push vs pull, hot keys (celebrities), denormalization, caching at scale.
~4 min read·updated 5/29/2026
33. Design Twitter / News Feed
The canonical fan-out problem. Tests: timeline construction, push vs pull, hot keys (celebrities), denormalization, caching at scale.
33.1 Requirements
Functional
- Post a tweet (text, optional media).
- Follow / unfollow users.
- Home timeline: feed of tweets from followees, reverse-chronological (or ranked).
- Profile timeline: a user's own tweets.
- Likes, retweets, replies.
- Notifications.
- Search.
Non-functional
- Read-heavy (~100:1 read/write).
- Low-latency timeline reads (P99 < 200ms).
- Eventual consistency OK (a few seconds delay for new tweets is fine).
- Scale: 300M MAU, 150M DAU, 75M tweets/day, 15B timeline reads/day.
33.2 Capacity (recap from chapter 2)
- Writes: ~3K tweets/sec peak.
- Reads: ~500K timeline reads/sec peak.
- Storage: ~30 GB tweets/day (text) + ~4 PB media/year (object storage).
- Cache: ~100 TB (last 800 tweets per active user).
33.3 High-level architecture
Mobile / Web
│
[ API gateway ]
│
[ Tweet service ] ←→ [ Tweet DB (sharded by author) ]
[ Timeline service ] ←→ [ Timeline cache (Redis) ]
[ Follow service ] ←→ [ Graph DB / Postgres ]
[ Media service ] ←→ [ Object storage (S3) + CDN ]
[ Notification service ]
[ Search service ] ←→ [ Elasticsearch ]
[ Fan-out service ] ←→ [ Kafka ]
33.4 Data models
Tweets
tweet_id (snowflake, time-ordered) | author_id | text | media_url | created_at | reply_to | retweet_of
Sharded by author_id.
Follow graph
follower_id | followee_id | created_at
Stored both directions for fast lookup. Sharded by follower_id for "who am I following" and by followee_id for "who follows me."
Timeline (precomputed, per user)
A list of tweet IDs sorted by time. Stored in Redis as a sorted set per user_id.
33.5 Push vs pull (the central trade-off)
Pull (read fan-out)
On timeline read: query each followee's recent tweets, merge, sort.
Pros: cheap writes (just save the tweet); always fresh. Cons: expensive reads — for a user following 1000 people, query 1000 tweet streams.
Bad at scale.
Push (write fan-out)
On tweet post: copy the tweet ID into every follower's timeline cache.
Pros: cheap reads — just read your timeline. Cons: expensive writes for users with many followers; wasted work for inactive users.
For typical user (200 followers): 200 cache writes per tweet. Manageable.
For celebrities (100M followers): 100M writes per tweet. Catastrophic.
Hybrid (Twitter's actual approach)
- Push for ordinary users: precompute timelines.
- Pull for celebrities: don't fan out; merge in at read time.
- Hybrid at read: read your precomputed timeline + query the few celebs you follow + merge.
This is the seminal Twitter design. Discussed in countless talks (Raffi Krikorian's especially).
Determining "celebrity"
Threshold (e.g., > 100K followers). Cache flag per user.
Inactive users
Don't fan out to inactive users (last login > 30 days). When they return, recompute their timeline on demand.
33.6 Timeline cache (Redis)
For each active user, store last 800 tweet IDs in a Redis sorted set:
timeline:user:42 → ZSET of (tweet_id, score=created_at)
Read: ZREVRANGE timeline:user:42 0 19 → 20 most recent tweet IDs → multi-get tweets.
Memory: 150M DAU × 800 IDs × 8 bytes = ~1 TB cache. Spread across 100s of Redis nodes.
33.7 Tweet storage
Append-mostly. Sharded by author_id. Postgres or Cassandra.
For Twitter scale: Manhattan (Twitter's KV) or Cassandra. For interview: shard Postgres or use Cassandra.
Index by author (timeline lookups), by time, by hashtag (via search index).
33.8 Media
Direct upload to S3 / GCS via signed URL. Background workers transcode (multiple resolutions, formats). CDN serves.
Tweet stores media_url; fan-out only references — no media duplication.
33.9 Posting a tweet (write path)
POST /tweets
→ Tweet service validates, generates tweet_id (Snowflake)
→ INSERT into Tweet DB (sharded by author_id)
→ Emit `TweetCreated` event to Kafka
→ 201 Created
Async:
Fan-out service consumes:
→ Look up author's followers (Follow service)
→ If author is celebrity: skip fan-out
→ Else for each active follower: ZADD timeline:user:<follower>
→ Trim timelines to 800 entries (ZREMRANGEBYRANK)
→ Index in Elasticsearch (Search service)
→ Trigger notification fan-out
33.10 Reading the home timeline
GET /home_timeline
→ Read precomputed cache: ZREVRANGE timeline:user:<me> 0 19 → tweet IDs
→ For each followed celeb (small list): query their recent tweets
→ Merge, dedupe, sort by time, take top 20
→ MGET tweets by ID (cache → DB)
→ Render
P99 < 200ms achievable: cached timeline is microseconds; tweet fetch is from cache mostly.
33.11 Ranked feed (post-2017 Twitter)
Reverse chrono replaced by ranked feed:
- Candidates: precomputed timeline + recent tweets from interest signals.
- Ranking: ML model scores by engagement probability.
- Ranked top N served.
Architecturally: candidate generation cheap (cache) + ranker (ML serving) at request time. Ranker is the hot service.
33.12 Search
- Tweets indexed in Elasticsearch as they're posted.
- Query: full text + filters (lang, time, has-media).
- Trending hashtags: Count-Min Sketch in real time (chapter 17) over the firehose.
33.13 Notifications
When you're mentioned, replied to, liked:
- Fan-out service emits
NotificationEvent. - Notification service writes to per-user list (Cassandra) and pushes to client (WebSocket / mobile push).
For users with millions of mentions (high-profile accounts), aggregate ("X liked your tweet" → "1.2M people liked your tweet").
33.14 Hot key handling (celebrity)
Celebrities create thundering herds:
- Their tweet is fetched 100M times in seconds.
- Their
tweet_id → tweet_objcache key is a hot shard.
Mitigations:
- Replicate hot tweets to multiple cache nodes; client picks random.
- Local cache in API servers for top-N tweets.
- CDN-cache tweet JSON (with short TTL).
33.15 Failure modes
- Cache cluster outage: fall back to DB; latency spikes; rate limit harder.
- Fan-out queue backed up: tweets visible on profile but slow to appear in timelines. Acceptable degradation.
- DB shard down: tweets from that shard unreadable; some users lose access to recent posts. Replicas help.
- Search index lag: search results slightly stale (seconds-minutes).
33.16 Replicas and geo
- Tweet DB primary in one region, replicas globally.
- Cache replicated.
- Reads served from local region.
- Writes still cross-region; user accepts a brief "still saving" UX.
For true multi-region writes, you'd need a more complex story (Spanner-class infra).
33.17 What you should sketch in interview
[ Client ]
│
[ API gateway ]
┌─┴───────────────────────────────┐
│ │
[ Tweet svc ] → [ Tweet DB ] → Kafka(TweetCreated) → [ Fan-out svc ] → [ Timeline cache (Redis) ]
│ └ [ Search indexer → ES ]
[ Timeline svc ] ← [ Timeline cache ] + (celeb tweets) ← [ Tweet DB ]
[ Follow svc ] ← [ Graph DB ]
[ Media svc ] → [ Object store + CDN ]
33.18 Common pitfalls
- Pure write fan-out without celebrity special case → catastrophic fan-out for top accounts.
- No timeline cache → DB melts.
- Synchronous fan-out → tweet latency is huge.
- No throttling on fan-out → Kafka-consumer lag explodes during viral moments.
- Storing media in DB → DB is hosed.
Key takeaways
- Hybrid push/pull: push to ordinary followers, pull for celebrities.
- Timeline cache (Redis sorted set) per user, last ~800 tweets.
- Async fan-out via Kafka decouples post latency from fan-out cost.
- Hot keys (celebrities) need explicit handling: replicate, CDN, dedup at query time.
- Scale by sharding the DB by author_id; cache + search + media on independent paths.
// 2 views