system-design

Design Twitter / X

Difficulty: Tier 2 Asked at: Meta, Twitter/X, Amazon, Careem, Noon, almost every mid+ loop Time budget: 45–60 min

The flagship social-media design question. Its whole soul is one decision — fan-out on write vs fan-out on read — and the “celebrity problem” that forces a hybrid. If you understand how a tweet gets from one person’s compose box into millions of timelines, you understand feed systems, and this pattern reappears in Instagram, news feed, and notifications.

Prerequisites: Caching, Message Queues, Hot Keys, Sharding


1. Requirements

Functional:

Non-functional:

Out of scope: DMs (that’s chat), search (a separate system), trends, ads.


2. Estimation


3. API

POST /tweets            { text, mediaIds? }         → 201 { tweetId }
GET  /timeline/home?cursor=...                      → [tweets]   (the hard one)
GET  /timeline/user/{userId}?cursor=...             → [tweets]
POST /follow/{userId}   /  DELETE /follow/{userId}
POST /tweets/{id}/like  /  /retweet

Timelines are cursor-paginated (not offset) — feeds are huge and change constantly.


4. Data model


5. The core decision: fan-out on write vs read

🚨 This is the entire question. How does a tweet reach followers’ home timelines?

Fan-out on write (push)

When you tweet, push the tweet ID into every follower’s precomputed timeline (in Redis) immediately.

Fan-out on read (pull)

When you open your timeline, pull recent tweets from everyone you follow and merge them on the fly.

The hybrid (what real Twitter does) ⭐


6. High-level design

flowchart TB
    Client -->|POST tweet| API[Tweet Service]
    API --> TweetDB[(Tweet store<br/>sharded by tweet_id)]
    API --> FanoutQ[[Fan-out queue]]
    FanoutQ --> FanoutW[Fan-out workers]
    FanoutW -->|normal user| Timelines[(Home timeline cache<br/>Redis, per user)]
    FanoutW -.celebrity: skip.-> Skip[No fan-out]

    Client -->|GET home timeline| TL[Timeline Service]
    TL --> Timelines
    TL -->|merge live| Celeb[Pull celebrity tweets]
    Graph[(Social graph)] --> FanoutW
    Graph --> TL

Post: store the tweet, enqueue a fan-out job. Workers look up followers and push the tweet ID into each normal follower’s Redis timeline. Celebrity tweets are not fanned out.

Read home timeline: read the precomputed Redis list, merge in a live pull of the celebrities you follow, sort by time, hydrate tweet IDs into full tweets (from cache/DB), return.


7. Deep dives

7a. Timeline storage & hydration

Store tweet IDs, not full tweets, in each timeline (compact; 800 IDs per user is tiny). On read, hydrate IDs → full tweets from a tweet cache (Redis) backed by the tweet store. This keeps timelines small and lets a tweet edit/delete reflect everywhere (single source of truth). Cap timelines (e.g. most recent 800) — nobody scrolls further; older pages fall back to a pull.

7b. The celebrity/hot-key problem

A celebrity’s tweet and profile are read by millions simultaneously — a classic hot key. Serve it from a heavily-cached/replicated path, and use the fan-out-on-read approach so you don’t do 100M writes. The hybrid split is the hot-key mitigation.

7c. Fan-out asynchronously

Fan-out happens in the background via a queue — the user’s POST returns as soon as the tweet is stored, not after 200M timeline writes. Workers process fan-out at their own pace. A slight delay before a tweet appears in all timelines is acceptable (eventual consistency). (Message Queues)

7d. Ranking (if asked)

Reverse-chronological is the simple version. A ranked feed scores candidate tweets (engagement, recency, affinity) with an ML model at read time over a candidate set — adds a ranking service between pull and render. Mention it as an extension; don’t rabbit-hole.

7e. Sharding

Shard tweets by tweet_id (Snowflake IDs are time-sortable → range or hash shard). Shard the social graph and timelines by user_id. A user’s timeline lives on one shard → single-shard read.


8. Bottlenecks & scaling further

  1. Timeline read load → precomputed timelines in Redis (fan-out on write) + hydration cache.
  2. Celebrity write explosion → fan-out on read for high-follower accounts (the hybrid).
  3. Hot tweets/profiles → caching + replication of hot keys.
  4. Fan-out worker load during a viral moment → queue buffers; autoscale workers.
  5. Storage growth → shard tweets; media in object storage + CDN.

9. Trade-off summary

Decision Chosen Alternative Why
Timeline building Hybrid fan-out Pure write / pure read Fast reads without celebrity write explosion
Timeline contents Tweet IDs + hydrate Full tweets copied Compact; single source of truth for edits/deletes
Consistency Eventual Strong A tweet appearing a second late is fine
Fan-out timing Async via queue Synchronous on POST POST stays fast; fan-out lags acceptably
Tweet store Wide-column, sharded Single relational DB Write scale + horizontal growth

10. Follow-up questions

Walk through exactly what happens when a normal user tweets. The POST hits the tweet service, which writes the tweet once to the sharded tweet store (assigning a time-sortable Snowflake ID) and immediately returns 201 to the client — the user isn't blocked. It also enqueues a fan-out job. A fan-out worker picks up the job, looks up the author's followers in the social graph, and for each *normal* follower pushes the new tweet's ID onto that follower's home-timeline list in Redis (capped at the most recent ~800). When those followers next load their timeline, the tweet ID is already there, so the read is a fast list fetch plus hydration — no per-read merging for this author. The whole fan-out is asynchronous, so a brief delay before the tweet appears everywhere is expected and fine.
Now a celebrity with 100M followers tweets. Why not do the same? Because fanning out one tweet to 100M timelines is 100M writes, and celebrities tweet often, so pure fan-out-on-write would create a colossal, bursty write load and waste effort on inactive followers. Instead, celebrity tweets are *not* pushed to timelines at all (fan-out on read for them). When any user loads their home timeline, the system reads their precomputed timeline (containing normal followees' tweets) and separately pulls recent tweets from the handful of celebrities that user follows, then merges the two by time. Since a user follows only a few celebrities, the live pull is cheap, and we avoid the 100M-write explosion. The follower-count threshold that classifies "celebrity" is a tunable knob.
Why store tweet IDs in the timeline instead of full tweets? Compactness and a single source of truth. A timeline of 800 tweet IDs is a few KB; 800 full tweets with text and media references is far larger and duplicated across every follower's timeline — hugely wasteful at hundreds of millions of users. Storing IDs and hydrating them from a shared tweet cache on read means each tweet exists once, so an edit or delete is reflected everywhere instantly (you don't have to rewrite it in millions of timelines), and timelines stay small and cheap to update during fan-out. The cost is an extra hydration step on read, which a tweet cache makes fast.
How does this stay consistent — what if fan-out lags? The system is intentionally eventually consistent. When fan-out workers are backed up (a viral spike), some followers see a tweet a bit later than others — which is perfectly acceptable for a social feed; nobody requires that a tweet appear in all 200M timelines at the same instant. The tweet itself is durably stored synchronously before the POST returns, so it's never lost; only its propagation into timelines is asynchronous. This trade — strong durability of the tweet, eventual propagation to feeds — is what lets the write path stay fast and the read path stay precomputed.
How would you add a ranked (non-chronological) feed? Insert a ranking stage between candidate generation and render. Instead of returning the merged timeline in pure reverse-chronological order, gather a candidate set (precomputed timeline + celebrity pulls + maybe some recommended tweets), then score each candidate with an ML model using features like recency, author affinity, predicted engagement, and media type, and sort by score. This runs at read time over a bounded candidate set so it stays fast, and it's an additive service — the fan-out/storage design underneath is unchanged. Keep it as a stated extension rather than the core, since chronological is the simpler, robust baseline.

11. What junior / mid / senior answers look like


Further reading