Knowledge graphSettings

Updated: April 30, 2026 at 7:40 AM

Twitter/X

medium

Classic case: home timeline generation, tweet fan-out strategy, search, trends, and very high-follower accounts.

Twitter/X gets hard not when one tweet is stored, but when that one write must turn into a fresh feed for millions of followers under an overwhelmingly read-heavy workload.

The chapter ties together fan-out strategy, home timeline caching, search, trends, and celebrity-account handling into one working architecture.

For interviews and architecture discussions, this case is useful because it forces you to explain where freshness can lag, how to survive very large accounts, and why one strategy does not fit every path.

Fan-out Strategy

The key choice is not the database first, but when tweets should be pushed into ready-made feeds and when they should be assembled on read.

Home Timeline Cache

The hottest read path usually depends on a prebuilt home timeline, or the cost of each request grows too quickly.

Streaming Trends

Detecting discussion spikes needs its own streaming path that can count fast without drowning in memory cost or noise.

Celebrity Accounts

Very large authors break naive fanout-on-write, which is why they almost always need a separate handling mode.

Introduction

Designing Twitter/X is not mainly about storing tweets. It is about keeping the home timeline fresh for millions of followers under a workload that is overwhelmingly read-heavy. The core architectural fork is deciding when fanout-on-write is worth the cost and when fanout-on-read is the safer option.

Functional Requirements

  • Tweet publishing — a short text post, media, and the basic interaction model around it.
  • Home timeline — a personalized feed built from accounts the user follows.
  • User timeline — the complete tweet history of one author in reverse chronological order.
  • Follow and unfollow — updates to the social graph.
  • Likes and retweets — engagement actions and counters.
  • Search — search across tweets, accounts, and keywords.
  • Trending topics — fast detection of emerging discussion spikes.
  • Notifications — mentions, replies, likes, and other activity around the account.

Non-functional Requirements

The user-facing priorities are low timeline latency, a predictable freshness window, and enough resilience to survive partial failure without large visible outages. Perfect consistency is not required here: a small delay before a tweet appears for all followers is usually acceptable.

  • Scale: roughly 500M registered users and 200M DAU.
  • Write volume: around 500M new tweets per day.
  • Traffic shape: reads outnumber writes by about 1000:1.
  • Home timeline latency: ideally under 200 ms.
  • Freshness window: 5-10 seconds of propagation delay can be acceptable.
  • Availability: a 99.99% target is reasonable, but the important part is graceful degradation instead of all-or-nothing failure.
  • Celebrity problem: publish flow must survive accounts with tens or hundreds of millions of followers.

Traffic estimate: 200M DAU × 100 timeline reads per day is roughly 20B reads per day, or about 230K QPS on the read side. 500M tweets per day is about 6K write QPS.

Core Problem: Feed Generation

The main architectural problem in Twitter/X is how to assemble the home timeline fast enough and cheaply enough when almost every tweet may need to reach many followers. At a high level, you either push tweets into ready-made timelines or you assemble the timeline on read from the latest tweets of the accounts a user follows.

Fanout-on-Write (Push)

As soon as a tweet is published, it is written into the cached timelines of the followers.

  • Fast reads because the timeline is already assembled.
  • Simple logic on the read path.
  • Expensive publishes for very large accounts.
  • High storage cost because entries are duplicated.
  • Some work is wasted on inactive followers.

Fanout-on-Read (Pull)

The timeline is assembled at request time by loading recent tweets from followed accounts and merging them into one response.

  • Publish cost is almost independent of follower count.
  • Less duplication in storage.
  • No background work for inactive users.
  • The read path becomes heavier and touches more sources.
  • Merge and filtering logic become part of the request path.

Hybrid Approach

In practice, Twitter/X uses a hybrid strategy: ordinary accounts use fanout-on-write, while very large accounts lean on fanout-on-read. That keeps the system from exploding on write amplification when one tweet would otherwise need to be copied into a massive number of personal timelines.

Hybrid Feed Architecture

Switch modes to highlight the path for ordinary accounts or high-follower authors.

Tweet Intake

API and media upload

Tweet Service

store tweet and choose strategy

Regular accounts: fanout-on-write

Fan-out Service

write into follower timeline caches

Home Timeline Cache

Redis ZSET per user

Celebrity accounts: fanout-on-read

Celebrity Store

separate path for very large accounts

Read-Time Merge

combine with the prepared feed

Timeline Service

rank, filter, and deliver

How celebrity accounts are handled

The threshold can be approximate, for example 10,000 followers. Once an account is above that level, the system avoids eagerly copying every new tweet into every follower timeline. Instead, the tweet is stored separately and merged into the home timeline during reads.

Home Timeline Cache Architecture

The timeline cache exists to make the hottest read path cheap. In most designs, publish is acknowledged quickly while the actual fan-out work moves into background processing so the user is not blocked on all downstream writes.

Timeline structure in Redis

# Each user has a dedicated home timeline in Redis
# Key: timeline:{user_id}
# Value: Sorted Set (score = timestamp, member = tweet_id)

ZADD timeline:12345 1705234567 "tweet_abc123"
ZADD timeline:12345 1705234890 "tweet_def456"

# Read the latest 100 tweets
ZREVRANGE timeline:12345 0 99

# Approximate storage estimate:
# 200M users × 800 tweets × 8 bytes = 1.28 TB
# With replication ×3 = about 4 TB Redis cluster

Cache limits

  • The cache usually keeps only the latest 800 tweets per user.
  • Older entries are evicted by rank.
  • Deep history is fetched from the primary data store.

Fan-out workers

  • Run asynchronously so publish ACK is not blocked.
  • Batch updates to reduce write amplification.
  • Need retries and duplicate-safe processing.

Trending Topics

Trend detection is a stream-processing problem. The system cares about bursts of discussion rather than absolute tweet count, so it needs approximate counting, time windows, and spam filtering that all move with the stream.

Trending Pipeline

Click a stage to highlight the corresponding part of the pipeline.

Tweet Stream

Kafka topic

Entity Extraction

hashtags and entities

Filtering

spam and stop words

Trend Scoring

windows and decay

Trend Cache

Redis sorted sets

Count-Min Sketch

A probabilistic data structure that lets you count millions of hashtags without a heavy exact table for every key. The trade-off is a small approximation error in exchange for low memory cost.

Sliding window

The system usually watches the last 5-15 minutes and gradually decays old events. That makes it easier to detect a real spike instead of a topic that has been slowly accumulating for hours.

Trend Scoring Formula

# Simplified Twitter trend score

def calculate_trend_score(topic, current_window):
    # Current velocity: tweets per minute over the last 5 minutes
    current_count = count_in_window(topic, minutes=5)

    # Baseline: average number of tweets per 5-minute bucket over 7 days
    baseline_count = get_baseline(topic, days=7)

    # How much faster the topic grows compared with normal
    if baseline_count > 0:
        velocity_ratio = current_count / baseline_count
    else:
        velocity_ratio = current_count * 10  # new topic bonus

    # Recency weight: exponential decay
    recency_weight = sum(
        tweet.weight * exp(-lambda * (now - tweet.timestamp))
        for tweet in current_window.tweets
    )

    # Engagement boost
    engagement_score = (likes + retweets * 2 + replies * 3) / total_tweets

    # Final score
    score = velocity_ratio * recency_weight * (1 + log(engagement_score))

    # Spam / bot filtering
    if unique_users_ratio < 0.3:  # too few unique users
        score *= 0.1  # heavy penalty

    return score

Timeline Ranking

Modern Twitter/X does not rely purely on chronology. It uses ranking to estimate the probability of engagement and move more relevant tweets higher in the home timeline.

Ranking features

Tweet features
  • Tweet age.
  • Presence of media.
  • Presence of links.
  • Text length.
  • Current engagement velocity.
  • Author's average engagement rate.
User features
  • Historical interactions with the author.
  • Topic interests.
  • Time-of-day activity patterns.
  • Distance in the follow graph.
  • Device type.
  • Current session context.

Two-pass ranking

Pass 1: candidate selection. The system gathers roughly 1000 tweets from the home-timeline cache, from celebrity tweets, and from recovery blocks such as “In case you missed it.”

Pass 2: final ranking. The ranking model scores each candidate, and the top part of the list is shown to the user. Inference is usually kept within tens of milliseconds.

Search Architecture

Search lives in a separate path: new tweets first land in a realtime index and later compact into on-disk segments. On the query path, the system parses the request, searches shards in parallel, and merges the results afterward.

Search Pipeline

Switch between the indexing path and the user query path.

Indexing Flow

Document Intake

tweet and tokenization

Realtime Index

in-memory Lucene shards

Cold Index

on-disk segments

Query Flow

Query Parsing

terms and filters

Shard Fan-out

parallel search

Merge and Rank

recency and engagement

Index latency ~10 s
Search p99 < 200 ms

Data Model

The primary store keeps normalized entities, while the hot read path relies on denormalized Redis structures and auxiliary indexes to stay fast.

# Core tables

tweets {
    tweet_id: UUID (Snowflake ID)
    user_id: UUID
    content: VARCHAR(280)
    media_urls: JSON
    created_at: TIMESTAMP
    reply_to_tweet_id: UUID (nullable)
    retweet_of_id: UUID (nullable)
    quote_tweet_id: UUID (nullable)
    like_count: INT
    retweet_count: INT
    reply_count: INT
}

users {
    user_id: UUID
    username: VARCHAR(15)
    display_name: VARCHAR(50)
    bio: VARCHAR(160)
    follower_count: INT
    following_count: INT
    is_verified: BOOLEAN
    is_celebrity: BOOLEAN  # follower_count > 10K
    created_at: TIMESTAMP
}

follows {
    follower_id: UUID
    followee_id: UUID
    created_at: TIMESTAMP
    PRIMARY KEY (follower_id, followee_id)
}

# Denormalized structures for reads
user_timelines (Redis Sorted Set)
    Key: timeline:{user_id}
    Score: tweet_timestamp
    Member: tweet_id

# Tweets from very large accounts
celebrity_tweets (Redis Sorted Set)
    Key: celebrity:{user_id}
    Score: tweet_timestamp
    Member: tweet_id

Snowflake ID Generation

Twitter created Snowflake ID to generate unique, roughly time-sortable identifiers without a centralized coordinator.

# Snowflake ID structure (64-bit)
┌────────────────────────────────────────────────────────────────┐
│ 1 bit │   41 bits timestamp   │ 10 bits │    12 bits          │
│unused │   (milliseconds)      │machine  │   sequence          │
│       │   since epoch         │   ID    │   number            │
└────────────────────────────────────────────────────────────────┘

# Properties:
# - Roughly sortable by time
# - No central coordination required
# - 4096 IDs per millisecond per machine
# - Enough range for decades before overflow

# Example ID: 1605978261000000000
# Timestamp: 2020-11-21 12:31:01 UTC
# Machine: 42
# Sequence: 0

def generate_snowflake_id(machine_id, last_timestamp, sequence):
    timestamp = current_time_ms() - TWITTER_EPOCH

    if timestamp == last_timestamp:
        sequence = (sequence + 1) & 0xFFF  # 12 bits
        if sequence == 0:
            timestamp = wait_next_millis(last_timestamp)
    else:
        sequence = 0

    id = (timestamp << 22) | (machine_id << 12) | sequence
    return id, timestamp, sequence

High-Level Architecture

At the top level, the design separates publishing, home-timeline reads, search, and trends into different paths. The important part is not to force them through one universal chain, but to give each path its own cost profile, caches, and failure behavior.

High-Level Architecture

Select a flow to highlight the key components.

Edge and Routing

Clients

web and mobile apps

CDN

static assets and media

Load Balancer

traffic routing

API Gateway

auth and rate limits

Core Services

Tweet Service

tweet intake and writes

Timeline Service

home feed assembly

Search Service

user query path

Trending Service

stream processing

User Service

profiles and follow graph

Async Layer and Data

Message Queue

Kafka and async fan-out

Tweets DB

primary storage

Home Timeline Cache

Redis ZSETs

Search Index

Lucene and Elasticsearch

Trending Cache

Redis sorted sets

Graph DB

follow graph

Interview Tips

Key trade-offs

  • Fanout-on-write vs fanout-on-read and why Twitter ends up hybrid.
  • The cost of caching versus the cost of assembling on demand.
  • Feed freshness versus response speed.
  • Trend accuracy versus the cost of stream processing.

Common follow-up questions

  • What do you do with an account that has 100M followers?
  • How do you scale the fan-out workers without hours of backlog?
  • How do you separate real trends from spam and bots?
  • How would you layer “For You” personalization on top of the base feed?

Additional Resources

Related chapters

Enable tracking in Settings