System Design Notes All designs

Social & Feeds

Twitter / X Timeline

Publish tweets, follow users, and read a home timeline of tweets from those you follow. The hard part is read scale and fan-out: a single celebrity tweet must reach 100M+ followers, while one timeline read merges thousands of authors into a ranked page in milliseconds.

Requirements

Functional

Non-functional

Scale & back-of-the-envelope

API design

POST   /v1/tweets            { text, mediaIds[] }   -> 201 { tweetId }
PATCH  /v1/tweets/{id}       { text }               -> 200
POST   /v1/tweets/{id}/replies | /likes | /retweets
POST   /v1/users/{id}/follow                        -> 200
GET    /v1/timeline?limit=20&cursor=<opaque>        -> { items[], nextCursor }
GET    /v1/search?q=...&cursor=<opaque>

Use opaque keyset cursors (createdAt + tweetId), never offset — offset re-scans skipped rows and breaks when new tweets arrive between pages.

High-level design

A CQRS-style system: the gateway forks into a write path (rate limiter → Tweet/Reply CRUD) and a read path (cache + Timeline Service). Tweet writes emit an event to a message queueTimeline FanoutTimeline Cache. CDC feeds Elasticsearch for search; media is served from the CDN.

flowchart LR
    Client["Web / Mobile"]
    CDN["CDN"]
    LB["Load Balancer L7"]
    GW["API Gateway"]
    RL{"Rate Limiter"}
    TweetSvc["Tweet CRUD Service"]
    TimelineSvc["Timeline Service"]
    SearchSvc["Search Service"]
    ProfileSvc["Profile Service"]
    Fanout["Timeline Fanout"]
    MQ["Message Queue"]
    TweetDB[("Tweet Content")]
    MediaDB[("Media Blob Store")]
    ES[("Elasticsearch")]
    TLCache[("Timeline Cache")]
    FollowDB[("Following Graph")]
    Client -->|HTTPS| LB --> GW
    CDN --> Client
    GW -->|write| RL --> TweetSvc
    GW -->|read| TimelineSvc
    GW --> SearchSvc
    GW --> ProfileSvc
    TweetSvc --> TweetDB
    TweetSvc --> MediaDB
    TweetSvc --> MQ --> Fanout -->|fanout on write| TLCache
    TweetDB -->|CDC| ES --> SearchSvc
    TimelineSvc --> TLCache
    TweetDB -->|fanout on read| TimelineSvc
    ProfileSvc --> FollowDB
    TweetDB --> CDN
    MediaDB --> CDN
      

Deep dive · hybrid fan-out

Normal authors → fan-out on write (push tweet IDs into each follower's Timeline Cache). Celebrities (followers above a threshold) → skip push to avoid a 100M-write burst; their tweets stay in Tweet Content and are pulled at read time and merged. The small per-celebrity result is cacheable, so millions of readers share one cached pull.

flowchart TD
    A["New tweet created"] --> B{"Author is celebrity?"}
    B -->|No| C["Fanout on write"]
    C --> D["Push tweet id to follower timelines"]
    B -->|Yes| F["Skip fanout, keep in Tweet Content"]
    H["Reader opens home timeline"] --> I["Read precomputed ids from cache"]
    I --> J{"Reader follows celebrities?"}
    J -->|Yes| K["Fanout on read celeb tweets"]
    J -->|No| L["Use cached ids only"]
    K --> M["Merge and rank"]
    L --> M
    M --> N["Return timeline page"]
      

Deep dive · write & read flows

The write is acked before fan-out completes; fan-out is async via the queue, so a celebrity burst never blocks the author's request.

sequenceDiagram
    actor U as User
    participant T as Tweet CRUD Service
    participant DB as Tweet Content
    participant MQ as Message Queue
    participant F as Timeline Fanout
    participant FG as Following Graph
    participant C as Timeline Cache
    U->>T: POST v1 tweets
    T->>DB: persist tweet
    T->>MQ: publish TweetCreated
    T-->>U: 201 Created
    MQ->>F: consume event
    F->>FG: get followers
    F->>C: push tweetId to timelines
      
sequenceDiagram
    actor U as User
    participant TL as Timeline Service
    participant C as Timeline Cache
    participant FG as Following Graph
    participant DB as Tweet Content
    U->>TL: GET v1 timeline
    TL->>C: read precomputed ids
    C-->>TL: tweet ids
    TL->>FG: get celebrity follows
    TL->>DB: fanout on read celeb tweets
    DB-->>TL: recent tweets
    TL->>DB: hydrate tweet ids
    TL-->>U: merged ranked page
      

Deep dive · Timeline Cache & hot keys

Data model

User Data (relational)   user_id PK, user_name, email, bio
Following Data (graph)   (:User)-[:FOLLOWS {since}]->(:User); per-user follower_count
Tweet Content (document) { tweetId, authorId, text, mediaIds[], createdAt, counts, isCelebrityAuthor }
Replies (document)       { replyId, tweetId, authorId, text, createdAt }
Media                    object store -> CDN
Timeline Cache           timeline:{userId} -> ZSET { tweetId : createdAt }, capped
Elasticsearch            { tweetId, authorId, text, createdAt }  kept fresh via CDC

Security

Scaling principles

Stateless services scale horizontally behind the LB; state lives in sharded/replicated stores; reads are served from caches + CDN; writes are decoupled through the message queue; consistency is relaxed to eventual on the timeline path to maximize availability.