Skip to content

02. Twitter/X Home Timeline — Low-Level Design

~18 min read · Part 3 of 4 (Overview → HLD → LLD → Q&A)

Data models

CREATE TABLE tweet (
    id          BIGINT PRIMARY KEY,     -- Snowflake (time-sortable)
    author_id   BIGINT NOT NULL,
    text        VARCHAR(280),
    media_id    UUID NULL,
    parent_id   BIGINT NULL,            -- reply target
    created_at  TIMESTAMP NOT NULL
);                                       -- sharded by id

CREATE TABLE retweet (                   -- a reference, NOT a copy
    id          BIGINT PRIMARY KEY,     -- Snowflake
    user_id     BIGINT NOT NULL,        -- who retweeted
    tweet_id    BIGINT NOT NULL,        -- the ORIGINAL tweet (flattened)
    created_at  TIMESTAMP NOT NULL
);

CREATE TABLE follow (follower_id BIGINT, followee_id BIGINT,
                     PRIMARY KEY(follower_id, followee_id));  -- + inverse index

-- Timeline cache (Redis):  tl:{user_id} = capped list of tweet_ids
-- Hot-object set (Redis):  hot_tweets = { tweet_id → velocity }, celeb posts

The retweet.tweet_id always points at the original tweet, even when someone retweets a retweet — the pointer is flattened at write time. This keeps hydration a single lookup and prevents retweet chains from ballooning.

Component internals

Fan-out service — dampened by hot-object detection

def on_tweet_event(evt):
    tid, author = evt.tweet_id, evt.author_id
    if hot_objects.is_hot(tid) or user_flags.is_celebrity(author):
        return                          # served via read-time merge, no fan-out
    for follower in graph.followers(author):
        timeline_cache.lpush_capped(f"tl:{follower}", tid, cap=800)

def on_retweet(evt):
    original = evt.tweet_id             # already flattened to the original
    if hot_objects.is_hot(original):
        return                          # viral: don't re-fan-out; it's a hot object
    for follower in graph.followers(evt.user_id):
        timeline_cache.lpush_capped(f"tl:{follower}", original, cap=800)

Both handlers share the guard: if the target tweet is hot, do nothing. Our breaking tweet crosses the velocity threshold within seconds; from that point its 1M retweets each hit is_hot → return, so the 415k-inserts/second storm collapses to a single hot object read by many timelines.

Hot-object detector — velocity threshold

def on_engagement(tweet_id):
    rate = velocity.increment(tweet_id)        # sliding-window retweets+likes / sec
    if rate > HOT_THRESHOLD and not hot_objects.is_hot(tweet_id):
        hot_objects.add(tweet_id)              # flip to pull path
        cache.warm(tweet_id)                   # pre-load body into hot-object cache

Detection must be early and cheap — a sliding-window counter per tweet, flipped the moment velocity crosses a low bar. Over-flagging is safe (it just moves a tweet to the cheap pull path); under-flagging is what causes the storm.

Timeline service — merge push list with hot objects

def build_timeline(viewer, cursor):
    pushed = timeline_cache.range(f"tl:{viewer}", cursor, limit=50)
    hot    = hot_objects.relevant_to(viewer)          # viral tweets + celebs followed
    merged = merge_by_id_time(pushed, hot)[:50]        # Snowflake ids sort by time
    return [hydrate(tid) for tid in merged]

def hydrate(tid):
    t = tweet_store.get(tid)                            # one lookup, even for retweets
    return { ...t, author: user_cache.get(t.author_id) }

Core algorithm — flattening retweet chains

retweet(user, target_id):
    t = tweet_store.get(target_id)
    original_id = t.original_id if t.is_retweet else target_id   # flatten
    insert retweet(user_id=user, tweet_id=original_id)
    emit Retweeted(tweet_id=original_id, user_id=user)

By always resolving to original_id, a retweet of a retweet of a retweet still points at one tweet. Hydration is therefore O(1) and the "retweet count" is a single counter on the original, not a traversal of a chain.

Sequence diagram — viral tweet dampening the storm

User      Tweet svc   Hot detector   Fan-out      Timeline cache   Reader   Timeline svc
 │ retweet  │             │             │              │            │           │
 ├──────────▶│ ref row    │             │              │            │           │
 │           ├─ Retweeted ─┼────────────▶│ is_hot? Y   │            │           │
 │           │             │             ├─ return (no fan-out)      │           │
 │           ├─ engagement ▶│ rate>thresh │              │            │           │
 │           │             ├─ add hot + warm cache       │            │           │
 │           │             │             │              │  GET /tl   │           │
 │           │             │             │              │            ├──────────▶│ merge push+hot
 │           │             │             │              │            │◀─ hot obj ─┤

Concurrency and edge cases

  • Detection race: many retweets arrive before the tweet is flagged hot. Accept a small burst of fan-out during the detection window; the sliding-window counter flips fast, and the pull path catches subsequent readers.
  • Retweet then delete original: hydration of a dangling reference returns a tombstone ("tweet unavailable"); references are not eagerly cleaned.
  • Ordering under clock skew: Snowflake worker/epoch bits bound cross-shard skew; timelines tolerate seconds of reordering.
  • Capped timeline overflow: beyond 800 ids, oldest drop; deep scroll falls back to a pull.
  • Duplicate in timeline: a tweet you receive both via push and via a hot-object merge is de-duped by id at merge time.
  • Live push vs durable timeline: the WebSocket push is best-effort; the durable timeline read is the source of truth, so a missed live event self-heals on next load.