Design Twitter / X
The interviewer asks
“Design Twitter. Users post short tweets, follow other users, and read a home timeline. Also support search over tweets and trending topics.”
What a strong answer looks like
A strong answer covers four subsystems and keeps each simple: tweet storage (Snowflake IDs, sharded by tweet ID, cached heavily), the home timeline (hybrid fan-out: push tweet IDs into Redis timelines for normal users, merge celebrity tweets at read time), search (near-real-time inverted index fed from Kafka, partitioned by time and tweet ID) and trends (streaming counts of hashtags/terms in sliding windows, compared to a baseline to find what is spiking). Reads dominate writes ~100:1, so the design is read-optimised. Numbers: ~250M DAU, ~6k tweets/s average with spikes of 50k+/s during big events, ~300k timeline reads/s. Interviewers expect the timeline fan-out maths, the celebrity problem, and a clear story for search and trends.
Concepts to mention
1 Requirements
Functional
- Post a tweet (up to 280 characters, optional media), reply, retweet and like
- Follow and unfollow users
- Home timeline: tweets and retweets from people you follow, newest or ranked
- User timeline: all tweets by one user
- Search tweets by keyword and hashtag, newest results within seconds
- Trending topics globally and per country/city
Non-functional
- Scale: 500M MAU, 250M DAU, 500M tweets/day
- Home timeline latency: p99 < 200 ms server side
- Tweet visible to followers within ~5 s (eventual consistency is fine)
- Search freshness: new tweets searchable within ~10 s
- Availability: 99.99% for reading; writes can briefly degrade
- Handle event spikes: 10x normal tweet rate during a World Cup goal or election result
Out of scope (say it out loud)
- Direct messages
- Ads and monetisation
- Media transcoding pipeline in detail
2 Estimates
3 API
| Endpoint | What it does |
|---|---|
POST/2/tweets | Create a tweet: text, media_ids, reply_to; returns tweet_id |
GET/2/timeline/home?cursor=&count=50 | Home timeline page using a max_id-style cursor |
GET/2/users/{id}/tweets?cursor= | User timeline |
POST/2/users/{id}/following | Follow a user |
GET/2/search?q=&cursor= | Search recent tweets by keyword or hashtag |
GET/2/trends?woeid= | Trending topics for a location |
4 Data model
tweetsSharded MySQL / Manhattan-style KV + Memcachedtweet_id (PK, Snowflake 64-bit)user_idtextmedia_idsreply_toretweet_ofcreated_atShard by tweet_id for even spread. Snowflake IDs embed time, so sorting by ID = sorting by time; no separate created_at index needed for timelines.
user_tweetsCassandra / wide-columnuser_id (partition)tweet_id (clustering, desc)User timeline is a single partition read; also the source for celebrity pull.
followsSharded graph store (FlockDB-like) on MySQL(follower_id, followee_id, created_at); stored in both directionsForward list for 'who do I follow', reverse list for fan-out. Follower counts kept separately.
home_timelineRedis Clustertl:{user_id} → list of (tweet_id, author_id, retweet flag)capped at ~800Only IDs. Replicated 3x. Built for active users only; rebuilt on demand.
search_indexLucene-based real-time index (Elasticsearch-like, Earlybird-style)term → posting list of tweet_ids; stored fields: user_idlangengagementPartitioned by tweet_id hash, tiered by time: in-memory segments for the last hours, SSD for older.
trend_countsFlink state + Redis(location, term, minute bucket) → countSliding-window counts and a longer baseline for spike detection; top-K per location written to Redis.
5 Architecture
Hover a box to highlight its connections.
6 Request flows
Post a tweet
- 1Client sends POST /tweets with an idempotency key; Tweet Service validates length and runs spam checks
- 2Tweet Service generates a Snowflake ID (timestamp + worker ID + sequence), writes the tweet and the user_tweets row, and caches it
- 3It publishes TweetCreated to Kafka (partitioned by author_id) and returns 201
- 4Fan-out workers read the author's follower list; if followers < threshold (e.g. 100k), they LPUSH the tweet ID to each active follower's Redis timeline and LTRIM to 800
- 5Search indexers add the tweet to the in-memory segment of its index partition; it is searchable within seconds
- 6Trends job tokenises hashtags and phrases and increments per-location counts in the current 1-minute window
Read the home timeline
- 1Timeline Service reads the user's cached ID list from Redis (one call, < 1 ms)
- 2It gets the list of celebrities the user follows (cached) and reads their latest tweet IDs from user_tweets
- 3It merges the lists by tweet ID (which is time order), removes duplicates and blocked/muted authors
- 4It hydrates tweets, authors and engagement counts with batched multi-gets from Memcached
- 5Optionally a ranking model reorders the top few hundred (for the 'For you' tab)
- 6Returns 50 tweets with a cursor = smallest tweet_id returned, so the next page asks for tweet_id < cursor
7 Deep dives
Timeline fan-out and the celebrity problem
The home timeline is the heart of the design. With reads ~100x writes, we precompute timelines: when a user tweets, push the tweet ID into each follower's Redis list. This is the approach Twitter described publicly: timelines in Redis, about 800 entries each, replicated 3 times. But an account with 100M followers would need 100M Redis writes per tweet, which takes minutes and wastes work on inactive followers. So we use a threshold: accounts above ~100k followers are not fanned out. Their tweets are pulled at read time from their user timeline and merged in. Because a user follows few such accounts, the merge is a handful of extra cache reads. Further tuning: only fan out to users active in the last ~30 days, rebuild a returning user's timeline by pull, and process fan-out for followers who are online first. Retweets are fanned out like tweets but with a 'retweet of' reference so the original is hydrated once. Fan-out is idempotent (dedupe by tweet ID) so Kafka replays are safe.
Search: real-time inverted index
Users expect a tweet to be searchable seconds after posting, so a nightly batch index is not enough. Indexers consume TweetCreated from Kafka, tokenise text (with language detection, because Indian Twitter mixes Hindi, Hinglish and English), and append to an in-memory index segment. Twitter's Earlybird design used an index optimised for appends where newest documents are searched first, because most queries want recent tweets. The index is partitioned by tweet ID hash across many shards, and each shard is replicated for read throughput. A query fans out to all partitions of the recent tier (scatter-gather), each returns its top results, and a broker merges them. Time tiering keeps the hot tier small: the last few days in RAM/SSD, older tweets in an archive tier queried only if needed. Ranking combines text relevance, recency, author reputation and engagement. Deletes are handled by a deleted-ID set checked at query time, then merged out during segment compaction.
Trending topics
Trending is not 'most frequent', otherwise 'good morning' trends every day. It is 'growing much faster than usual'. A stream processor (Flink) reads tweets, extracts hashtags and n-grams, and counts them per location in 1-minute buckets. For each term it keeps a short window count (last 15 minutes) and a baseline (same hour over past days, or an exponentially decayed average). The trend score is roughly the ratio of current rate to expected rate, with a minimum volume and a boost for many distinct users (so one bot farm cannot trend a tag). Counting every term exactly is costly, so use a Count-Min Sketch plus a heap for top-K candidates, then exact counts only for candidates. Per location (India, Mumbai, Bengaluru) the top 30 are written to Redis every minute. Before publishing, a safety filter removes spam, abusive terms and coordinated manipulation; this step is a real requirement, not an afterthought.
IDs, ordering and pagination
Tweet IDs are 64-bit Snowflake IDs: 41 bits of milliseconds since a custom epoch, 10 bits of worker ID and 12 bits of sequence, so each worker can make 4,096 IDs per ms without coordination. Because IDs are roughly time-ordered, every list (home timeline, user timeline, search results) can be sorted and paged by ID. Pagination uses max_id/since_id cursors: 'give me tweets with ID < X'. This is stable when new tweets arrive at the top, unlike offset pagination, which would show duplicates. Clock skew between workers can produce slightly out-of-order IDs; this is acceptable for a timeline. Workers must never move their clock backwards: if NTP steps time back, the generator waits or refuses until time catches up.
8 Trade-offs
Push vs pull for timelines
Push makes reads cheap at the cost of write amplification and storage; pull makes writes cheap and reads expensive. Twitter's read:write ratio favours push with a pull exception for big accounts. State the threshold and that it is tuned from metrics.
Chronological vs ranked timeline
Chronological ('Following') is predictable and easy to page. Ranked ('For you') adds out-of-network recommendations and engagement models, at the cost of a candidate-generation and ranking layer. Offer both; the storage layer above serves both.
Exact vs approximate counts
Like and retweet counts and trend counts do not need to be exact in real time. Approximate, batched counters (or sketches) cut write load massively. Exact counts can be reconciled in batch.
9 Bottlenecks
Event spikes
During a World Cup final, tweets/s can jump 10x within seconds. Kafka absorbs the burst; fan-out lag grows briefly. Autoscale fan-out workers on consumer lag, and pre-scale before known events (IPL final, elections).
Hot tweet reads
A viral tweet is read millions of times per minute. Cache it in Memcached with replicas and a small in-process cache in Timeline Service so one cache shard does not melt.
Search fan-out
Every query hits every partition of the recent tier. As partitions grow, add replicas for throughput and keep the hot tier small through time tiering.
10 Failure handling
Redis timeline node lost
Timelines are a cache with replicas; if all copies are lost, the Timeline Service rebuilds by pull (merge recent tweets from followed users) and repopulates. Slower first load, no data loss.
Fan-out backlog
If workers stall, tweets still appear in user timelines and search; home timelines are stale. Kafka keeps the events; workers catch up. Celebrity pull path is unaffected.
Search cluster partition down
Queries return partial results from healthy partitions with a flag rather than failing. Replicas take over; a rebuilt partition replays from Kafka.
11 What impresses
- Quoting the fan-out maths and why the threshold exists
- Explaining trends as spike detection against a baseline, not raw counts
- Search freshness via an append-optimised in-memory index fed by Kafka
- Using time-sortable IDs for all pagination
- Handling Indian multilingual and Hinglish text in search tokenisation
- Rebuild-by-pull as the recovery path for the timeline cache
12 Mistakes
- SELECT ... WHERE user_id IN (followees) ORDER BY time on every timeline load
- Fanning out to all 100M followers of a celebrity
- Using offset pagination on a live timeline
- Treating trends as the most frequent hashtags of all time
- Building search with SQL LIKE queries
- Spending all the time on posting and none on reading
13 Follow-ups
Lessons this design uses
Practice questions
- Model the follow relationship for a social app with 300M users. We need 'who do I follow', 'who follows me', and 'does A follow B'.
- On a Twitter-like app, a celebrity with 50 million followers posts. Estimate the fan-out cost if you push the post into every follower's timeline, and decide what to do.
Saved on this device. Create a free account to keep it on every device.
Practise this with Sam
A real mock interview, out loud, with an honest scorecard. Free to start.