Technology Aug 30, 2026 · 4 min read

HLD: Twitter / X

Design a social media platform where users post tweets, follow each other, and see a timeline of tweets from accounts they follow. 1️⃣ Clarify Requirements Functional Requirements Post a tweet (text, images, videos) Follow / unfollow users View home timeline (tweets from...

DE
DEV Community
by Gouranga Das Samrat
HLD: Twitter / X

Design a social media platform where users post tweets, follow each other, and see a timeline of tweets from accounts they follow.

1️⃣ Clarify Requirements

Functional Requirements

  • Post a tweet (text, images, videos)
  • Follow / unfollow users
  • View home timeline (tweets from followed users, newest first)
  • Like, retweet, reply
  • User profile with tweet history
  • Search tweets

Non-Functional Requirements

  • High availability — Twitter is a utility
  • Eventual consistency — timeline can be slightly stale (seconds)
  • Low latency — timeline load < 200ms
  • Read-heavy — 100:1 read-to-write ratio
  • Scale: 300M DAU, 500M tweets/day

2️⃣ Estimate Scale

Tweets/day: 500M
Write QPS:  500M / 86,400 ≈ 5,800 writes/sec
Read QPS:   5,800 × 100 = 580,000 reads/sec
Peak read:  ~1-2M reads/sec

Storage:
  Tweet: 280 chars = ~300 bytes + metadata ≈ 1 KB
  500M tweets/day × 1 KB = 500 GB/day
  Media: ~100 TB/day (images, videos)

Timeline cache:
  300M users × 100 tweet IDs × 8 bytes = 240 GB

3️⃣ API Design

POST /tweets
Body: { text, mediaIds[] }
Response: { tweetId, createdAt }

GET /timeline/home?cursor=&limit=20
Response: { tweets: [...], nextCursor }

GET /users/:userId/tweets?cursor=
Response: { tweets: [...], nextCursor }

POST /tweets/:tweetId/likes
DELETE /tweets/:tweetId/likes

POST /users/:userId/follow
DELETE /users/:userId/follow

4️⃣ The Core Problem: Home Timeline

The hardest part. User A follows 500 people. How do you fetch their timeline?

Approach 1: Pull (Fan-out on Read)

Timeline request:
1. Get all 500 followed user IDs
2. Query tweets from each user (last 24h)
3. Merge and sort by time
4. Return top 20

❌ Slow: 500 DB queries per timeline request

❌ Gets worse as you follow more people

❌ Doesn't scale at 580K reads/sec

Approach 2: Push (Fan-out on Write) — Twitter's Original Approach

User A tweets:
1. Find all A's followers (e.g., 1,000 followers)
2. Push tweet ID to each follower's timeline cache
3. Timeline request → just read from your cached timeline
Tweet created → [Fan-out Service]
                    → Redis: user1_timeline.lpush(tweetId)
                    → Redis: user2_timeline.lpush(tweetId)
                    → Redis: user3_timeline.lpush(tweetId)
                    ... (1,000 followers)

✅ Timeline reads are O(1) — just read from Redis

❌ Celebrity problem: Lady Gaga has 100M followers → 100M Redis writes per tweet

❌ Wasted if followers don't check timeline

Approach 3: Hybrid (Twitter's Current Approach)

Regular users (< 1M followers): Fan-out on Write
Celebrity users (> threshold):  Fan-out on Read

Timeline request:
1. Fetch pre-computed timeline from Redis (fan-out writes)
2. Fetch recent tweets from celebrity accounts you follow (fan-out reads)
3. Merge both, sort, return

5️⃣ High-Level Architecture

[Client]
   │
   ▼
[CDN] ← static assets, media
   │
[API Gateway + LB]
   │
   ├──────────────────────────────────────┐
   │                                      │
[Tweet Service]                   [Timeline Service]
   │                                      │
   ├─► [Media Service] → [S3/CDN]         ├─► [Timeline Cache (Redis)]
   │                                      │         (500 tweet IDs per user)
   └─► [Tweets DB]                        └─► [Fan-out Service]
       (Cassandra: write-heavy,                   │
        time-series)                        [Message Queue (Kafka)]
                                                  │
                                        [Worker Pool: fan-out writers]
                                                  │
                                          [Timeline Cache Redis]

6️⃣ Database Design

Tweets Table (Cassandra)

CREATE TABLE tweets (
  tweet_id     BIGINT,          -- Snowflake ID (time-sortable)
  user_id      BIGINT,
  text         TEXT,
  media_ids    LIST<UUID>,
  created_at   TIMESTAMP,
  likes_count  COUNTER,
  retweet_count COUNTER,
  PRIMARY KEY (user_id, tweet_id)  -- partition by user, sort by tweet
) WITH CLUSTERING ORDER BY (tweet_id DESC);

Why Cassandra?

  • Write-heavy (500M tweets/day)
  • Time-series access pattern (recent tweets)
  • Naturally partitioned by user_id

Followers Table (Graph in Cassandra)

-- Who does user X follow?
CREATE TABLE following (
  follower_id  BIGINT,
  followed_id  BIGINT,
  created_at   TIMESTAMP,
  PRIMARY KEY (follower_id, followed_id)
);

-- Who follows user X?
CREATE TABLE followers (
  followed_id  BIGINT,
  follower_id  BIGINT,
  created_at   TIMESTAMP,
  PRIMARY KEY (followed_id, follower_id)
);

Denormalized for O(1) lookup in both directions.

Timeline Cache (Redis)

Key: timeline:{userId}
Value: Sorted Set of tweet IDs (score = timestamp)
Size: Keep last 800 tweet IDs
TTL: 7 days (trim inactive users)

7️⃣ Media Storage

Tweet with image:
1. Client uploads image → [Media Service]
2. Media Service → S3 (original)
3. Media Service → async resize: thumbnail, medium, large
4. All sizes stored in S3
5. CloudFront CDN in front of S3
6. Tweet stores mediaId → resolved to CDN URL on read

8️⃣ Search

Tweets → Elasticsearch
Index: tweet_id, text, user_id, created_at, hashtags

Search query → Elasticsearch → ranked results
Trending hashtags → pre-computed every 5 min, stored in Redis

9️⃣ The Celebrity (Hotspot) Problem

Elon Musk tweets → 100M followers
Fan-out on write: 100M Redis writes in seconds → IMPOSSIBLE

Solution:
- Maintain a "high-follower" list
- Skip fan-out for celebrities
- At read time: fetch celebrity tweets separately, merge with pre-computed timeline
- Cache celebrity recent tweets (they're read by millions)

🎨 Diagram

The diagram shows:

  • Full architecture: Client → CDN → Gateway → Services
  • Fan-out on write flow (Kafka → workers → Redis)
  • Hybrid timeline assembly (pre-computed + celebrity merge)
  • Cassandra tweet storage partition scheme
  • Media upload and CDN serving path

✅ Trade-offs Summary

Decision Choice Rationale
Timeline generation Hybrid push/pull Pure push fails for celebrities
Tweet storage Cassandra Write-heavy, time-series
Timeline cache Redis Sorted Set O(log N) insert, O(1) range read
Fan-out Async via Kafka Don't block tweet creation
Media S3 + CDN Scalable object storage
Consistency Eventual Timeline can be 1-2s stale — fine
DE
Source

This article was originally published by DEV Community and written by Gouranga Das Samrat.

Read original article on DEV Community
Back to Discover

Reading List