Full Stack • Java • System Design • Cloud • AI Engineering

Twitter System Design - 1 Hour Interview Guide

Design a scalable microblogging platform like Twitter/X. Covers requirements, capacity estimation, tweet creation, home timeline, fan-out strategies, trending topics, search, notifications, database design, caching, and trade-offs for a 1-hour system design interview.


1-Hour Interview Roadmap

Time Topic
0 – 5 min Requirements clarification
5 – 10 min Capacity estimation
10 – 18 min High-level architecture + core components
18 – 28 min Tweet creation + fan-out strategy
28 – 38 min Home timeline — assembly + caching
38 – 46 min Search + trending topics
46 – 54 min Database design + scaling
54 – 60 min Trade-offs + failure scenarios

What Are We Building?

A real-time microblogging platform where users post short messages (tweets), follow other users, and see a reverse-chronological timeline of tweets from people they follow.

Scale reference: Twitter/X has 350 million monthly active users, 200 million daily active users, 500 million tweets posted per day, and serves 800 million timeline reads per day. The defining challenge is the asymmetric follow model — celebrities with 100M+ followers post tweets that must appear in millions of timelines within seconds.


Step 1 — Requirements

Functional Requirements

# Requirement
1 Users can post tweets (text up to 280 characters, optional media)
2 Users can follow and unfollow other users
3 Users see a home timeline — tweets from accounts they follow, reverse-chrono
4 Users can view any public user's profile timeline
5 Users can like, retweet, quote-tweet, and reply to tweets
6 Users can search tweets and users by keyword or hashtag
7 System shows trending topics (hashtags) — globally and by region
8 Users receive notifications for likes, retweets, replies, new followers
9 Tweets can include photos, videos, polls, and links
10 Users can create and join Twitter Spaces (live audio — out of scope)

Non-Functional Requirements

# Requirement
1 Home timeline load latency < 200ms (p99)
2 Tweet posted must be visible to followers within 5 seconds
3 High availability — 99.99% uptime
4 Eventual consistency acceptable for timeline and counts
5 Strong consistency for tweet creation — tweet must be durable once ACKed
6 System must handle extreme skew — users with 100M+ followers
7 Search must support real-time indexing — new tweets searchable within seconds
8 Trending topics updated every 5 minutes

Out of Scope

  • Twitter Spaces (live audio)
  • Twitter Blue / subscription features
  • Ads and promoted content
  • Content moderation and trust & safety pipeline

Step 2 — Capacity Estimation

Assumptions

Metric Value
Daily Active Users (DAU) 200 million
Monthly Active Users (MAU) 350 million
Tweets per day 500 million
Average tweet size (text+meta) 300 bytes
Media tweets (%) 30%
Average media size 500 KB
Average follows per user 300
Timeline loads per user/day 10
Tweets shown per load 20
Read-to-write ratio ~100:1

Write Volume

500 million tweets/day
= ~5,800 tweets/second (sustained)
= ~15,000 tweets/second (peak)

Timeline Read Volume

200M DAU × 10 loads/day = 2 billion timeline reads/day
= ~23,000 reads/second (sustained)
= ~70,000 reads/second (peak)

Fan-out Volume

Average user: 300 followers
500M tweets/day × 300 = 150 billion fan-out writes/day
= ~1.7 million fan-out writes/second (sustained peak)

Storage

Text:  500M/day × 300 bytes  = 150 GB/day
Media: 150M/day × 500 KB     = 75 TB/day

5-year text storage:  ~270 TB
5-year media storage: ~135 PB

Key Insight

Twitter is read-heavy and write-fan-out-heavy. The home timeline is the core product. The central design challenge is the same as Instagram/Facebook: how do you deliver a celebrity's tweet to 100 million follower timelines within 5 seconds? — but with a tighter latency SLA and a purely chronological (not ranked) timeline.


Step 3 — High-Level Architecture

flowchart TD
    Client --> AG[API Gateway\nAuth + Rate Limiting]

    AG --> TS[Tweet Service]
    AG --> TLS[Timeline Service]
    AG --> US[User Service]
    AG --> SS[Search Service]
    AG --> TRD[Trending Service]
    AG --> NS[Notification Service]

    TS --> KF[Kafka\ntweet.created events]
    TS --> TDB[(Tweet Store\nCassandra)]

    KF --> FO[Fan-out Service]
    KF --> IDX[Search Indexer]
    KF --> NW[Notification Workers]
    KF --> TRDW[Trending Workers]

    FO --> TC[(Timeline Cache\nRedis)]
    FO --> TLDB[(Timeline Store\nCassandra)]

    TLS --> TC
    TC -->|miss| TLDB

    US --> UDB[(User Store\nPostgreSQL)]
    US --> GDB[(Follow Graph\nCassandra)]

    SS --> ES[(Elasticsearch\nTweet Index)]
    TRD --> TRDB[(Trending Store\nRedis)]

    NW --> FCM[FCM / APNs]

Component Responsibilities

Component Responsibility
API Gateway JWT auth, rate limiting (writes: 300/15min per user), routing
Tweet Service Validate, persist, and publish tweet creation events
Fan-out Service Write tweet IDs to follower timelines — hybrid push/pull model
Timeline Service Assemble and return home timeline from cache or persistent store
User Service Registration, profile management, follow/unfollow
Search Service Real-time tweet and user search via Elasticsearch
Trending Service Compute and serve trending hashtags by region and globally
Notification Svc Deliver async like/retweet/reply/follow notifications
Search Indexer Consume tweet events from Kafka, index into Elasticsearch
Trending Workers Consume tweet events, count hashtag frequency, update trending store

Step 4 — Tweet Creation Flow

What Happens When You Post a Tweet

sequenceDiagram
    participant User
    participant TS as Tweet Service
    participant TDB as Tweet Store (Cassandra)
    participant KF as Kafka
    participant FO as Fan-out Service
    participant TC as Timeline Cache (Redis)

    User->>TS: POST /tweets {text, media_url}
    TS->>TS: Validate (length, rate limit, spam check)
    TS->>TS: Assign tweet_id via Snowflake ID
    TS->>TDB: Persist tweet
    TDB-->>TS: OK
    TS-->>User: ACK {tweet_id, created_at}

    TS->>KF: Publish tweet.created {tweet_id, author_id, timestamp}

    KF->>FO: Consume event
    FO->>GDB: GET followers(author_id) — paginated
    loop Per batch of 1,000 followers
        FO->>TC: ZADD timeline:{follower_id} score=timestamp tweet_id
    end

Key design decisions:

  • User gets ACK before fan-out begins — tweet is durable, fan-out is async
  • Snowflake ID ensures tweet IDs are globally unique and time-sortable
  • Fan-out via Kafka — decouples tweet persistence from timeline delivery

Tweet Validation Rules

Check Rule Response on failure
Length Text ≤ 280 characters 400 Bad Request
Rate limit 300 tweets per 15 minutes per user 429 Too Many Requests
Duplicate detection Same text from same user within 30 seconds 409 Conflict
URL expansion Convert short URLs to canonical form Applied silently
Media attachment Max 4 images or 1 video 400 if exceeded

Media Upload Flow

Twitter follows the same pre-signed upload pattern as Instagram:

flowchart LR
    User -->|1. Request upload token| MS[Media Service]
    MS -->|2. Pre-signed S3 URL| User
    User -->|3. Upload directly to S3| S3[Object Storage]
    S3 -->|4. Complete| MS
    MS -->|5. Return media_url| User
    User -->|6. POST tweet with media_url| TS[Tweet Service]
    S3 --> CDN[CDN Edge]

Step 5 — Fan-out Strategy

The Celebrity Problem on Twitter

Twitter's follow model is asymmetric (like Instagram, unlike Facebook's bidirectional friends). This creates extreme skew:

User type Followers Fan-out writes per tweet
Regular user ~300 300 writes
Micro-influencer ~50K 50,000 writes
Celebrity (e.g., Elon Musk) 150M+ 150 million writes

A single tweet from a 150M-follower user cannot fan out synchronously — it would take hours.

Hybrid Fan-out Model

flowchart TD
    TweetEvent -->|author follower count| CHK{Follower\nCount}

    CHK -->|≤ 1M followers\nRegular + Influencer| PUSH[Fan-out on WRITE\nPush tweet_id to all\nfollower timeline caches\nasync, batched]

    CHK -->|> 1M followers\nCelebrity| SKIP[Skip fan-out write\nDo NOT push to timelines]

    TimelineLoad[Follower loads timeline] --> ASSM[Timeline Assembler]
    ASSM -->|Pre-built cache| RegTweets[Regular accounts' tweets\nfrom Redis timeline cache]
    ASSM -->|Live pull| CelebTweets[Celebrity tweets\nfetch from Tweet Store\nfor each celebrity followed]
    ASSM -->|Merge| Final[Sort by timestamp DESC\nReturn top 20]

Push path — regular users (≤ 1M followers)

  1. Fan-out Service consumes tweet.created from Kafka
  2. Fetches follower list from social graph in batches of 1,000
  3. For each follower: ZADD timeline:{follower_id} {timestamp} {tweet_id} in Redis
  4. Each timeline cache holds the most recent 800 tweet IDs per user

Pull path — celebrities (> 1M followers)

  1. No writes to follower caches — celebrity tweets are never pushed
  2. Twitter maintains a separate Celebrity Hot Cache per user
  3. When Bob loads his timeline, Timeline Service:
    • Fetches Bob's pre-built timeline from Redis (regular accounts)
    • Fetches the last 20 tweets from each celebrity Bob follows (from Tweet Store)
    • Merges both sets and sorts by tweet_id (time-ordered) descending

Why 1M as the threshold?

1M followers × 15,000 peak tweets/sec from high-follower accounts
= 15 billion fan-out writes/second — impossible

With hybrid model:
Only users < 1M push-fan-out
Celebrities are ~0.001% of accounts but create 20%+ of timeline views

Fan-out Parallelism

flowchart LR
    KF[Kafka\ntweet.created\n200 partitions] --> FO1[Fan-out Worker 1]
    KF --> FO2[Fan-out Worker 2]
    KF --> FO3[Fan-out Worker N]
    FO1 -->|Redis pipeline\n1000 ZADD/batch| RC[(Redis Cluster)]
    FO2 --> RC
    FO3 --> RC
  • Kafka partitioned by author_id — all events from one author go to one partition
  • Fan-out workers each own a set of Kafka partitions
  • Redis pipeline batching: send 1,000 ZADD commands in a single round trip

Step 6 — Home Timeline Assembly

Timeline Cache Structure (Redis Sorted Set)

Key:    timeline:{user_id}
Type:   Sorted Set
Score:  tweet_id (Snowflake — encodes timestamp)
Member: tweet_id

ZREVRANGE timeline:12345 0 19  →  [tweet_id_999, tweet_id_998, ..., tweet_id_980]
  • Score = tweet_id (not timestamp) — Snowflake IDs are already time-ordered
  • ZREVRANGE returns most recent tweets first
  • Each user's timeline holds at most 800 tweet IDs
  • TTL: 48 hours — inactive users' caches are evicted; rebuilt on next login

Timeline Read Flow

sequenceDiagram
    participant Bob
    participant TLS as Timeline Service
    participant TC as Timeline Cache (Redis)
    participant TLDB as Timeline Store (Cassandra)
    participant RANK as Celebrity Merger Service
    participant TDB as Tweet Store (Cassandra)
    participant PC as Post Cache (Redis)

    Bob->>TLS: GET /home_timeline?count=20

    TLS->>TC: Fetch timeline ZSET

    alt Cache hit
        TC-->>TLS: tweet_id list
    else Cache miss
        TLS->>TLDB: Query timeline table
        TLDB-->>TLS: tweet_id list
        TLS->>TC: Rebuild cache
    end

    TLS->>RANK: Fetch celebrity tweets
    RANK->>TDB: Query top tweets per celebrity
    TDB-->>RANK: tweet IDs
    RANK-->>TLS: merged tweet IDs

    TLS->>PC: Fetch cached posts
    PC-->>TLS: cached posts (partial)

    TLS->>TDB: Fetch missing posts
    TDB-->>TLS: full tweet data

    TLS-->>Bob: Final timeline response (20 tweets)

Pagination

Cursor-based, not offset-based:

First load:  GET /home_timeline?count=20
Response:    { tweets: [...], next_cursor: "tweet_id_XYZ" }

Next page:   GET /home_timeline?max_id=tweet_id_XYZ&count=20
Response:    { tweets: [...older tweets...], next_cursor: "..." }

max_id is the oldest tweet_id seen — return all tweets with ID < max_id. This is stable even when new tweets arrive at the top.

User Timeline (Profile Page)

When viewing someone's profile, return their tweets directly:

GET /users/:username/tweets

Query: SELECT * FROM tweets WHERE author_id = ? ORDER BY tweet_id DESC LIMIT 20

No fan-out needed — profile timeline reads directly from the Tweet Store partitioned by author_id.


Step 7 — Tweet Store and Database Design

Database Selection

Data Database Reason
Tweets Cassandra Write-heavy, time-ordered, append-only, billions of rows
Home timeline Redis (hot) + Cassandra (cold) Fast sorted set reads; durable backup
Follow graph Cassandra Wide rows — billions of edges, fast range read by user_id
Users & profiles PostgreSQL ACID, structured, strong consistency
Likes & retweets Cassandra Very high write volume, append-only
Search index Elasticsearch Full-text, hashtag, real-time indexing
Trending counters Redis INCR per hashtag per time window — atomic, fast
Notifications Cassandra Append-only, high volume

Tweet Table (Cassandra)

Table: tweets

Partition key:  author_id    BIGINT
Clustering key: tweet_id     BIGINT DESC   (Snowflake — newest first)

Columns:
  text           TEXT
  media_urls     LIST<TEXT>    (CDN URLs)
  reply_to_id    BIGINT        (null if original tweet)
  retweet_of_id  BIGINT        (null if original tweet)
  like_count     COUNTER
  retweet_count  COUNTER
  reply_count    COUNTER
  quote_count    COUNTER
  hashtags       SET<TEXT>
  mentions       SET<BIGINT>   (user_ids of @mentions)
  language       VARCHAR
  is_deleted     BOOLEAN
  created_at     TIMESTAMP

Query patterns:

  • Profile timeline: WHERE author_id = ? ORDER BY tweet_id DESC LIMIT 20
  • Single tweet fetch: WHERE author_id = ? AND tweet_id = ?
  • Batch fetch (timeline hydration): WHERE (author_id, tweet_id) IN (...)

Follow Graph (Cassandra)

Table: followers          (who follows this user — for fan-out)
  user_id        BIGINT
  follower_id    BIGINT
  created_at     TIMESTAMP
  PRIMARY KEY (user_id, follower_id)

Table: following          (who this user follows — for timeline assembly)
  user_id        BIGINT
  followed_id    BIGINT
  created_at     TIMESTAMP
  is_celebrity   BOOLEAN    (true if followed_id has > 1M followers)
  PRIMARY KEY (user_id, followed_id)

The is_celebrity flag is denormalized onto the following table — Timeline Service reads it to know which accounts to pull vs use from cache.

Timeline Store (Cassandra)

Table: home_timeline

Partition key:  user_id      BIGINT
Clustering key: tweet_id     BIGINT DESC   (time-ordered, newest first)

Columns:
  author_id      BIGINT
  inserted_at    TIMESTAMP

PRIMARY KEY (user_id, tweet_id)
  • Acts as cold storage for users who are inactive (cache miss)
  • Holds up to 3,000 tweet IDs per user — older entries are TTL-expired

Like and Retweet Tables (Cassandra)

Table: likes
  tweet_id    BIGINT
  user_id     BIGINT
  liked_at    TIMESTAMP
  PRIMARY KEY (tweet_id, user_id)   — unique per (tweet, user)

Table: retweets
  tweet_id        BIGINT
  retweeter_id    BIGINT
  retweet_tweet_id BIGINT            (the new tweet_id created for the retweet)
  created_at      TIMESTAMP
  PRIMARY KEY (tweet_id, retweeter_id)

Step 8 — Search

Requirements

  • Search tweets by keyword, hashtag, or phrase
  • Search users by username or display name
  • Results must include tweets posted within the last few seconds
  • Support autocomplete on hashtags and usernames

Why Elasticsearch

Feature Elasticsearch Cassandra / PostgreSQL
Full-text search Native inverted index LIKE '%keyword%' = full table scan
Hashtag search Term query — instant Requires separate index table
Real-time indexing Near real-time (< 1 second NRT) Not designed for search
Relevance ranking BM25 scoring built-in Not available
Autocomplete search_as_you_type field type Not supported
Horizontal scale Sharding + replication built-in Difficult for search workloads

Real-time Indexing Pipeline

flowchart LR
    KF[Kafka\ntweet.created] --> IDX[Search Indexer\nService]
    IDX --> ES[Elasticsearch\nTweet Index]
    ES -->|< 1 second NRT| SearchAPI[Search API]
  • Search Indexer consumes tweet.created events from Kafka
  • Indexes tweet text, author, hashtags, timestamp, language, location (if provided)
  • Near Real-time (NRT): Elasticsearch refreshes its index every 1 second by default
  • New tweets are searchable within ~2 seconds of posting

Tweet Index Schema (Elasticsearch)

{
  "tweet_id":    "long",
  "author_id":   "long",
  "author_name": "search_as_you_type",
  "text":        "text (analyzed, language-aware)",
  "hashtags":    "keyword[]",
  "mentions":    "long[]",
  "language":    "keyword",
  "created_at":  "date",
  "like_count":  "long",
  "retweet_count": "long",
  "location":    "geo_point"
}

Search Flow

flowchart LR
    User -->|"#SystemDesign"| SS[Search Service]
    SS -->|bool query\nhashtags:SystemDesign| ES[Elasticsearch]
    ES -->|Top 50 matching tweet IDs + scores| SS
    SS -->|Batch fetch full tweets| TC[(Redis\nTweet Cache)]
    TC -->|misses| TDB[(Cassandra\nTweet Store)]
    SS --> User
flowchart LR
    KF[Kafka\ntweet.created] --> TW[Trending Workers]
    TW -->|ZINCRBY trending:global 1 hashtag| RC[(Redis\nSorted Sets)]
    TW -->|ZINCRBY trending:us 1 hashtag| RC
    TW -->|ZINCRBY trending:in 1 hashtag| RC
    RC -->|ZREVRANGE trending:global 0 9| TAPI[Trending API]
    TAPI --> User

Twitter uses time-windowed counting — not raw total volume, but rate of increase:

Trending Score = (tweets_in_last_30_min / tweets_in_last_6_hours) × recency_factor

A topic that suddenly spikes from 100 tweets/hour to 50,000 tweets/hour trends immediately, even if another topic has 1 million total tweets.

Redis Sliding Window Implementation

Every 5 minutes, rotate time bucket:
  Key:   trending:{region}:{time_bucket}
  Type:  Sorted Set
  Op:    ZINCRBY trending:global:{bucket} 1 "#SystemDesign"

Trending score = sum of counts across last 6 × 5-min buckets
               weighted by recency (recent buckets weighted higher)

Top 10:  ZREVRANGE trending:global:{current_bucket} 0 9

Old buckets are expired automatically via TTL (6 hours).


Step 9 — Notifications

Notification Events

Trigger Event Recipient(s) Priority
Like on tweet Tweet author Low
Retweet Original tweet author Medium
Reply Tweet author + mentioned users High
Quote tweet Original tweet author Medium
New follower The user being followed Medium
@mention in tweet Mentioned user High
Direct Message Recipient High

Async Notification Pipeline

flowchart LR
    TS[Tweet Service] -->|tweet.created| KF[Kafka]
    LKS[Like Service] -->|like.created| KF
    FLW[Follow Service] -->|follow.created| KF

    KF --> NW[Notification Workers]
    NW -->|Check preferences| PREF[(Redis\nUser Prefs)]
    NW -->|User online?| SESS[(Redis\nSessions)]
    NW -->|Online| WS[WebSocket / SSE\nIn-app notification]
    NW -->|Offline| FCM[FCM / APNs\nPush notification]
    NW -->|Store| NDB[(Cassandra\nNotification Store)]

Notification Batching

For high-volume events (a viral tweet getting 10,000 likes in a minute):

  • Do not send 10,000 individual push notifications to the author
  • Batch: send "You have 10,000+ new likes" every 5 minutes
  • Redis counter tracks unsent notification count per user per event type
  • Flush and notify on a schedule, not per-event

Step 10 — Caching Strategy

Redis Cache Map

Cache Key Pattern TTL Contents
Home timeline timeline:{user_id} 48 hrs Sorted Set — tweet_id → score (time-ordered)
Tweet details tweet:{tweet_id} 7 days Full tweet object (text, counts, media)
User profile user:{user_id} 1 hr Profile data, follower/following counts
Like check liked:{tweet_id}:{user_id} 1 hr Boolean — did user like this tweet?
Like count likes:{tweet_id} Eventual Counter — async flushed to Cassandra
Retweet count retweets:{tweet_id} Eventual Counter
Celebrity recent tweets celeb_tweets:{user_id} 2 min Last 50 tweet IDs — for pull-path merge
Trending (global) trending:global 5 min Sorted Set — hashtag → trending score
Trending (by region) trending:{region} 5 min Sorted Set per region
Follow graph (hot) following:{user_id} 30 min List of followed user IDs (for timeline merge)
Active sessions session:{user_id} 30 days Device tokens for push notifications

Viral Tweet Hot Spot

A tweet from Elon Musk gets 5 million likes in 10 minutes. If every like writes to Cassandra:

5,000,000 likes / 600 seconds = ~8,333 writes/second to ONE partition

This creates a Cassandra hot partition that degrades performance.

Solution:

flowchart LR
    CLIENT["User Like Action"]

    REDIS["Redis Counter (INCR likes)"]

    FLUSH["Background Flush Worker"]

    DB["Cassandra Persistent Store"]

    CACHE["Redis Read Cache"]

    CLIENT --> REDIS
    REDIS --> FLUSH
    FLUSH --> DB
    DB --> CACHE

  • All like increments hit Redis INCR — O(1), sub-millisecond, no contention
  • Async worker flushes accumulated counts to Cassandra every 30 seconds
  • Tweet reads always served from Redis cache — Cassandra never hot-spotted on reads

Step 11 — Rate Limiting

Twitter has aggressive rate limits to prevent spam and API abuse.

Write Rate Limits

Operation Limit per user Window
Post tweet 300 15 min
Follow user 400 24 hrs
Like tweet 1,000 24 hrs
Retweet 300 24 hrs
Direct message 500 24 hrs

API Read Rate Limits (Developer API)

Endpoint Basic tier Enterprise tier
Home timeline 15 req / 15 min 900 req / 15 min
User tweets 15 req / 15 min 1,500 req / 15 min
Search tweets 180 req / 15 min 450 req / 15 min

Rate Limiting Architecture

flowchart LR
    Client --> AG[API Gateway]
    AG -->|Sliding window check| RC[(Redis\nRate Limit Counters)]
    RC -->|Under limit| Service[Downstream Service]
    RC -->|Over limit| ERR[429 Too Many Requests\nRetry-After header]

Algorithm: Token Bucket or Sliding Window Counter in Redis.

Key:   rate:{user_id}:{endpoint}:{window}
Op:    INCR + EXPIRE
Check: if count > limit → reject with 429

Step 12 — Scaling

Read Scaling (Timeline)

flowchart TD
    LB[Load Balancer] --> T1[Timeline Pod]
    LB --> T2[Timeline Pod]
    LB --> T3[Timeline Pod]
    T1 & T2 & T3 --> RC[(Redis Cluster\nTimeline Cache)]
    RC -->|miss| CS[(Cassandra\nTimeline Store + Tweet Store)]
  • Timeline Service is stateless — add pods horizontally
  • Redis Cluster distributes timeline keys across shards
  • Cassandra read replicas absorb cache misses

Write Scaling (Fan-out)

flowchart LR
    KF[Kafka\n200 partitions\nauthor_id key] --> FW1[Fan-out Worker]
    KF --> FW2[Fan-out Worker]
    KF --> FW3[Fan-out Worker]
    FW1 & FW2 & FW3 -->|Redis PIPELINE\n1000 ZADD/batch| RC[(Redis Cluster)]
    FW1 & FW2 & FW3 -->|async| CS[(Cassandra\nTimeline Store)]
  • Kafka partitioned by author_id — fan-out for one author always on one partition
  • Workers process follower batches of 1,000 using Redis pipelining
  • Scale fan-out workers independently from tweet creation workers

Database Sharding

Database Shard Key Notes
Tweets author_id All tweets from one user co-located on one shard
Timeline user_id Each user's timeline on one shard
Follow graph user_id All edges for one user co-located
Likes tweet_id All likes for one tweet on one shard
Search Elasticsearch sharding built-in Shard by tweet_id range

Handling the Celebrity Follow Unfollow Problem

When a celebrity (100M followers) is followed by a new user:

  • Do not retroactively fan-out their historical tweets to the new follower's cache
  • Timeline Service handles this: on first load after a new follow, pull the last 20 tweets from the newly followed celebrity and inject into the response
  • Cache warms progressively with subsequent real-time fan-out events

Step 13 — Multi-Region Deployment

flowchart TD
    Users --> GLB[Global Load Balancer\nAnyCast DNS]
    GLB -->|US users| US[US-East\nPrimary]
    GLB -->|EU users| EU[EU-West\nReplica]
    GLB -->|APAC users| APAC[APAC\nReplica]

    US -->|async replication| EU
    US -->|async replication| APAC
Operation Strategy
Tweet creation Write to local region, async replicate to others (eventual)
Timeline read Served from local region replica — sub-50ms
Search Elasticsearch cluster per region; tweet index replicated
Trending Per-region trending computed locally; global trending aggregated
Consistency Eventual — a tweet may take 3–10s to appear globally

Step 14 — Failure Scenarios

Failure Impact Mitigation
Redis (timeline cache) down All timeline reads fall to Cassandra Redis Sentinel / Cluster auto-failover; Cassandra is source of truth
Kafka down Fan-out and indexing pause 3-broker Kafka cluster; ISR replication; producers retry with backoff
Fan-out workers down New tweets don't fan out to followers Kafka retains events (7-day retention); catch up on restart
Cassandra node down Partial reads/writes rerouted RF=3; coordinator retries on healthy replicas
Elasticsearch down Search unavailable Graceful degradation — show cached trending; search returns 503
Trending service down Trending tab empty Serve cached last-known trending list; TTL = 30 min fallback
Media upload fails Tweet posted without media Pre-signed URL expires in 15 min; client retries; tweet stays DRAFT
Hot fan-out (viral user) Fan-out workers backed up Kafka consumer lag alert; auto-scale fan-out workers; skip inactive users
Rate limiter Redis down Rate limits unenforced Fail open (allow traffic) for 60s; Redis Sentinel restores quickly
Notification service down Delayed notifications Notifications stored in Cassandra first; delivered when service recovers

Final Architecture

flowchart TD
    Mobile[Mobile / Web Clients] --> GLB[Global Load Balancer]
    GLB --> AG[API Gateway\nAuth + Rate Limiting]

    AG --> TS[Tweet Service]
    AG --> TLS[Timeline Service]
    AG --> US[User Service]
    AG --> SS[Search Service]
    AG --> TRD[Trending Service]

    TS --> KF[Kafka Cluster\nauthor_id partitioned]
    KF --> FO[Fan-out Workers × N]
    KF --> IDX[Search Indexer]
    KF --> NW[Notification Workers]
    KF --> TW[Trending Workers]

    FO --> TC[(Redis Cluster\nTimeline Cache)]
    FO --> TLDB[(Cassandra\nTimeline Store)]

    TLS --> TC
    TC -->|miss| TLDB
    TLS --> TDB[(Cassandra\nTweet Store)]

    US --> UDB[(PostgreSQL\nUser Profiles)]
    US --> GDB[(Cassandra\nFollow Graph)]

    IDX --> ES[(Elasticsearch\nTweet Index)]
    SS --> ES
    TW --> TRDB[(Redis\nTrending Counters)]
    TRD --> TRDB

    NW --> FCM[FCM / APNs]
    NW --> NDB[(Cassandra\nNotifications)]

    TDB --> CDN[CDN\nMedia Delivery]

Technology Stack

Layer Technology
API Gateway NGINX / Envoy / Kong
Tweet + Timeline Svc Scala / Java / Go
Message queue Apache Kafka (200+ partitions)
Timeline + trend cache Redis Cluster
Tweet + timeline store Apache Cassandra
User profiles PostgreSQL (primary + read replicas)
Follow graph Apache Cassandra
Search + hashtags Elasticsearch (6+ shards per index)
Object storage Amazon S3 / Google Cloud Storage
Video transcoding FFmpeg workers / AWS MediaConvert
CDN Fastly / Akamai / CloudFront
Push notifications FCM (Android) + APNs (iOS)
Monitoring Prometheus + Grafana + ELK + Jaeger
Deployment Kubernetes, multi-region

Key Trade-Offs

Decision Option A Option B Choice & Reason
Timeline ordering Chronological ML-ranked by relevance Chronological (Twitter's core identity); ML ranking optional "For You" tab
Fan-out strategy Push only Pull only Hybrid — push for < 1M followers; pull for celebrities
Fan-out timing Synchronous Async via Kafka Async — user gets ACK immediately; fan-out completes in < 5 seconds
Timeline storage Redis only Redis + Cassandra Both — Redis for hot users; Cassandra for cold/inactive users
Like counts Exact row-level increment Redis counter + async flush Redis counter — prevents Cassandra hotspot on viral tweets
Search Cassandra text scan Elasticsearch Elasticsearch — real-time NRT indexing, full-text, hashtag, geo search
Trending Real-time per-event counters Time-windowed sliding buckets Sliding window — rate-of-increase matters more than total volume
Pagination Offset-based (?page=2) Cursor-based (?max_id=...) Cursor — stable under real-time inserts; no duplicate tweets
Notification delivery Synchronous per event Batched async via Kafka Async batched — 10K likes should not send 10K push notifications

Twitter vs Instagram vs Facebook — Design Comparison

Dimension Twitter Instagram Facebook News Feed
Social model Asymmetric follow Asymmetric follow Bidirectional friends + pages
Timeline ordering Reverse-chronological ML-ranked ML-ranked (EdgeRank)
Fan-out threshold ~1M followers ~1M followers ~100K followers
Feed complexity Lower — sort by time Medium — ML ranking High — ML + multi-content type
Search Real-time tweet search User + hashtag search User + page search
Trending topics Yes — core feature Explore page Trending section
Content types Short text + media Photo + video only Text, photo, video, link, share
Key design challenge Celebrity fan-out + real-time NRT Media upload + fan-out Feed ranking + social graph complexity

Common Interview Mistakes

  • ❌ Not explaining the celebrity fan-out problem and the 1M-follower threshold
  • ❌ Designing a synchronous fan-out — blocks tweet creation at peak load
  • ❌ Not distinguishing home timeline (fan-out) from user/profile timeline (direct DB read)
  • ❌ Using offset pagination — creates duplicate/skipped tweets on a live feed
  • ❌ Storing like counts with DB row-level increments — Cassandra hot partition on viral tweets
  • ❌ No caching layer — 70K timeline reads/sec cannot be served from Cassandra directly
  • ❌ Using LIKE '%keyword%' for search — full table scan at billions of tweets
  • ❌ Trending topics based on total volume, not rate of increase — misses what's actually trending
  • ❌ No cold-start plan for inactive users whose timeline cache has expired
  • ❌ No rate limiting discussion — Twitter's rate limits are a core part of its API design

Interview Questions

  1. How does Twitter's home timeline differ from a user's profile timeline?
  2. What is the fan-out problem? How does Twitter handle a tweet from a user with 100M followers?
  3. Why is fan-out done asynchronously via Kafka?
  4. How do you decide whether to push a tweet (fan-out on write) or pull it (fan-out on read)?
  5. How is the home timeline stored in Redis? What data structure is used?
  6. What happens when an inactive user's timeline cache has expired (cold start)?
  7. How do you handle the case where a new follower needs to see a celebrity's historical tweets?
  8. Why is cursor-based (max_id) pagination better than offset pagination for Twitter?
  9. How is Elasticsearch used for real-time tweet search?
  10. How do trending topics work? What is the difference between volume and rate-of-increase?
  11. How do you prevent a viral tweet from hot-spotting a single Cassandra partition?
  12. How does Twitter rate limiting work technically?
  13. What happens if the Redis timeline cache cluster goes down?
  14. How do you ensure tweet ordering is preserved in Cassandra using Snowflake IDs?
  15. How would you design the "For You" algorithmic timeline tab alongside the chronological tab?

Summary

Concern Solution
Tweet persistence Cassandra — partitioned by author_id, clustered by tweet_id DESC
Fan-out Hybrid — push to Redis for ≤1M followers; pull from Tweet Store for celebrities
Fan-out pipeline Async Kafka — tweet ACK returned before fan-out; workers consume in parallel
Home timeline cache Redis Sorted Set per user — tweet_ids scored by Snowflake ID
Timeline cold start Rebuild from Cassandra timeline store; celebrities merged at read time
Like/retweet counts Redis INCR counters — async flush to Cassandra every 30 seconds
Search Elasticsearch — NRT indexing via Kafka; full-text + hashtag + geo
Trending topics Redis sliding window buckets — rate-of-increase, not total volume
Notifications Async Kafka pipeline — batched to avoid 10K push notifications per viral tweet
Rate limiting Redis sliding window counters at API Gateway
Media storage Pre-signed S3 upload + CDN delivery — no media through app servers
Scale Stateless services + Kafka partitioning + Redis Cluster + Cassandra RF=3
Multi-region Read local, write primary, async replication, eventual consistency (< 10s)

The core principle: Twitter's home timeline is a fan-out delivery problem. The push-pull hybrid at the 1M-follower threshold, combined with Redis sorted sets for fast assembly, is what makes real-time chronological timelines possible at 200M daily users.