Design Twitter Interview Questions | JiQuest

add

#

Design Twitter

System design deep dive · HLD

Design Twitter / X: full high-level design.

Requirements, back-of-envelope capacity estimation, a high-level design diagram, a deeper deployment architecture diagram, sequence diagrams for posting a tweet and reading a home timeline, and an entity-relationship diagram - with the fan-out-on-write vs fan-out-on-read trade-off and the celebrity-account problem worked through in detail.

250MDaily active users
~6K/sTweets written (avg)
~1000:1Effective read : write
Authorposts a tweet Fan-out servicepushes to followers Follow graphwho follows author Follower timelinescache updated Celebrity checkskip push, pull later 1M+ followers → pulled at read time

1. Clarify requirements before drawing any box

Twitter is a fan-out problem wearing a social-network costume: the hard part is not storing a 280-character tweet, it's delivering it to the right set of home timelines fast enough, at a scale where some accounts have hundreds of millions of followers and others have a dozen.

Functional requirements

Post a tweetText (280 chars), optional media, optional reply-to/quote reference.
Follow / unfollowDirected, asymmetric relationship - following someone does not require reciprocity.
Home timelineReverse-chronological (or ranked) feed of tweets from accounts the user follows.
Like / retweetLightweight engagement actions that update counters visible on the tweet.

Non-functional requirements

Low timeline latencyOpening the app and seeing a feed is the core loop; target <200ms p99 for a cached timeline.
Write availability under loadA viral event (breaking news) must not take down the ability to post or read.
Eventual consistency is fineA follower seeing a new tweet a few seconds late is acceptable; losing a tweet is not.
Skewed fan-outFollower counts are extremely long-tailed - the design must not assume a uniform "few hundred followers."
Explicitly out of scope Full-text search, trending-topics detection, direct messages, and ad ranking are called out as separate systems built on top of this core rather than part of the core timeline design.

2. Back-of-the-envelope capacity estimation

The number that shapes this entire design is not tweets/sec - it's how many timeline-cache writes one tweet can trigger. That single multiplier is why fan-out can't be a single uniform strategy.

MetricAssumptionResulting estimate
Tweets posted (writes)500M tweets/day across 250M DAU~5,800 writes/sec average, ~20,000/sec peak (breaking news)
Home timeline reads250M DAU × ~20 feed refreshes/day~58,000 reads/sec average, ~250,000/sec peak
Naive fan-out amplificationMean ~200 followers/account (heavily skewed by celebrities)500M tweets/day × 200 ≈ 100B potential cache writes/day if every tweet fanned out to every follower
Tweet storage~300 bytes/tweet (text + metadata, media stored separately)500M/day × 300B × 5yr ≈ 274 TB of tweet rows
Timeline cache footprintCache last ~800 tweet IDs per active user, ~100 bytes/entry250M users × 800 × 100B ≈ 20 TB across the Redis fleet
Why this matters The fan-out amplification row is the number that forces a hybrid strategy: pushing every tweet to every follower's cache works fine for a typical account, but for an account with 100M followers it would mean 100M writes for a single tweet - which is why celebrity accounts are handled as a special case rather than uniformly.

3. High-level design (HLD)

The HLD splits the write path (post a tweet, fan it out) from the read path (assemble a home timeline), and introduces the follow graph as a service both paths depend on but neither owns.

Clientweb / mobile API gatewayL7, auth Tweet servicewrite path Timeline serviceread path Follow graph Fan-out queueKafka, per-tweet job Timeline cacheRedis, per-user list Tweet storeCassandra Fan-out workerswrite to caches
Stateless servicesFast-path infraDurable storageAsync / workers

What each box owns

Tweet service (write path)

Validates and persists the tweet to the Tweet store, then enqueues a single fan-out job onto the Kafka topic and returns to the caller immediately - it never waits for fan-out to complete. This is what keeps "post a tweet" fast regardless of the author's follower count.

Fan-out workers + queue

Consume fan-out jobs, look up the author's followers from the Follow graph, and push the new tweet ID onto each follower's timeline cache list - but only for authors under a follower-count threshold (see the deep dive on the celebrity problem below). For accounts above the threshold, no push happens at all.

Timeline service (read path)

Reads the requester's pre-computed timeline cache (already-fanned-out tweet IDs), separately fetches recent tweets from any celebrities the user follows (fan-out-on-read), merges and re-sorts the two lists by time, then hydrates full tweet objects for the final page. This hybrid merge is the crux of the whole design.

Follow graph and Tweet store

The Follow graph is a dedicated adjacency-list service (who-follows-whom, who-does-X-follow) since both fan-out and celebrity-pull need fast graph lookups it shouldn't share a database with tweet content. The Tweet store is a wide-column store (Cassandra-style) partitioned by tweet ID, since tweets are immutable and accessed by ID or author, never by complex query.

4. Detailed architecture diagram

The architecture diagram is where the celebrity problem becomes concrete: a threshold check that routes two categories of accounts down structurally different fan-out paths, plus the replica and partitioning counts an interviewer expects to see justified.

Edge layer CDN (media/static) API gateway + L7 LB Rate limiterper-user posting limits Celebrity threshold checkfollower_count > 1M? Write path (fan-out-on-write, normal accounts) Tweet svc ×8 pods Kafka fan-out topic Fan-out workers ×30 Redis timeline cache (sharded) Read path (merges pushed + pulled tweets) Timeline svc ×20 pods Merge + hydrate + rank Pull: celebrity tweets Read-through tweet cache Tweet store (Cassandra) Partition A-M Partition N-Z partitioned by tweet_id, 3-way replicated Follow graph store Adjacency list, sharded by user_id follower_count cached alongside edge list Engagement pipeline Like/retweet counters (Kafka) batched async increments, not synchronous
DecisionChoiceReasoning
Fan-out strategyHybrid: push for <1M followers, pull for ≥1MBounds worst-case fan-out writes per tweet to a manageable number while keeping the common-case read cheap (pre-computed).
Follow graph as its own serviceSeparate from Tweet storeBoth the write path (who to fan out to) and read path (who am I following) need low-latency graph lookups independent of tweet volume.
Tweet store partitioningPartition by tweet_idTweets are immutable and always fetched by ID during hydration; no need to co-locate by author.
Engagement countersAsync batched increments via KafkaA like/retweet burst on a viral tweet must never turn into a hot-row lock contention problem on the primary tweet record.

5. Sequence diagrams for the two critical flows

These two flows are exactly where the fan-out-on-write vs fan-out-on-read trade-off has to be shown as call order, not just described in prose.

5.1 Post a tweet (fan-out-on-write, normal account)

Author Tweet svc Tweet store Fan-out queue Timeline cache 1. POST /tweets {text} 2. INSERT tweet 3. tweet_id 4. enqueue fan-out job 5. 201 Created {tweet_id} 6. worker: for each follower, push tweet_id 7. skip entirely if author ≥ 1M followers

Step 5 returns to the author before fan-out has even started - the client never waits on it. Step 6/7 run entirely asynchronously, seconds after the response was sent, and are the exact place the celebrity threshold check happens: for a huge account this step is skipped altogether, and those tweets are picked up at read time instead.

5.2 Read a home timeline (hybrid push + pull merge)

Client Timeline svc Timeline cache Follow graph Tweet store 1. GET /timeline 2. LRANGE cached tweet_ids 3. ~800 ids 4. which follows are celebrities? 5. pull their latest tweets 6. merge, sort by time, hydrate 7. 200 OK {timeline page}

Step 2/3 is the fast, common case - most of the timeline was already pushed there when it was posted. Steps 4/5 only add latency proportional to how many celebrities the requester follows, which is small and bounded, unlike the alternative of pulling from every followed account. Step 6 is where fan-out-on-write and fan-out-on-read results are reconciled into one ordered page.

6. Entity-relationship (ER) diagram and schema

The data model has to answer: where does a tweet live, how is an asymmetric follow relationship stored so both directions can be queried, and what exactly does the timeline cache hold (never full tweet bodies).

users PK id BIGINT handle VARCHAR display_name VARCHAR follower_count BIGINT is_celebrity BOOLEAN created_at TIMESTAMP tweets PK id BIGINT FK author_id BIGINT text VARCHAR(280) FK reply_to_id BIGINT NULL media_urls LIST<TEXT> created_at TIMESTAMP follows FK follower_id BIGINT FK followee_id BIGINT created_at TIMESTAMP timeline_cache FK user_id BIGINT tweet_ids LIST<BIGINT> max_len 800 updated_at TIMESTAMP 1N 1..N NN follows is a self-join on users (follower_id, followee_id both -> users.id)

Key modeling decisions

follows stores both directions implicitlyA composite key on (follower_id, followee_id) plus a reverse index on followee_id lets "who do I follow" and "who follows me" both be indexed lookups.
timeline_cache stores IDs only, never tweet bodiesKeeps the cache small and means an edited/deleted tweet is reflected everywhere the next time it's hydrated from the Tweet store.
tweets is append-only and partitioned by tweet_idDeletes are rare and handled as tombstones; the table never needs an update-heavy "last edited" workflow.
is_celebrity is a materialized flag, not computed per readRecomputing "is this a celebrity" from a live COUNT on every fan-out decision would itself become a bottleneck.
Storage choiceUse whenWatch out for
Wide-column (Cassandra) for tweetsAccess pattern is get-by-id and get-by-author at extreme write volume.No multi-row transactions; fan-out and counters must be handled as separate, eventually-consistent writes.
Graph-oriented store for followsYou expect frequent "mutual follows" or "suggested accounts" queries later.Adds an extra system to operate; a well-indexed relational table is often enough for pure follow/unfollow.

7. Deep dives interviewers actually probe

Why not just fan out every tweet to every follower, always?

Because follower count is a power-law distribution: most accounts have a few hundred followers, where fan-out-on-write is cheap and gives near-instant timeline reads, but a small number of accounts have tens of millions. Fanning out one tweet from a 100M-follower account would mean 100M cache writes in a burst, overwhelming the fan-out workers and the cache cluster at the exact moment the tweet matters most. The hybrid design pushes for normal accounts and pulls for celebrities at read time, capping worst-case fan-out cost per tweet regardless of who posted it.

What happens to a follower's cached timeline when they follow someone new?

The new followee's recent tweets are not retroactively injected into the follower's existing cache - that would require scanning the followee's whole history. Instead, the follower's cache simply starts receiving future pushes from that account (if non-celebrity) or gets that account included in the read-time pull set (if celebrity) going forward, and the client is expected to backfill a small amount of the followee's recent history via a direct "load their profile" call the first time.

How do you keep like/retweet counters accurate without hot-row contention?

Every like or retweet publishes an event to Kafka rather than issuing a synchronous UPDATE tweets SET like_count = like_count + 1. A stream aggregator batches events per tweet over a short window (e.g. 500ms) and applies one combined increment, so a viral tweet receiving thousands of likes per second produces orders of magnitude fewer writes to the underlying row, and the counter is allowed to be a few hundred milliseconds stale in exchange for not serializing on a single row.

How does deleting a tweet propagate through a system with pre-computed caches?

Because timeline_cache stores only tweet IDs, a delete only needs to write a tombstone in the Tweet store; every timeline that still references the deleted ID simply omits it at hydration time (the hydrate step filters out IDs the Tweet store returns as deleted or not-found). This is exactly why the cache was designed to hold IDs instead of denormalized tweet content in the first place - it avoids having to hunt down and rewrite every fanned-out copy.

What's the single biggest bottleneck as this scales 10x?

Not the read path - the hybrid fan-out already bounds per-tweet cost. The real risk is the Follow graph service during a mass event (e.g. a huge account gaining millions of new followers in hours, or many normal accounts posting during breaking news simultaneously, each triggering their own fan-out fan-out job). The mitigation is the same pattern used elsewhere in the design: keep the graph lookups cheap and cacheable, and let the fan-out queue's consumer group scale out horizontally to absorb backlog rather than trying to make any single write synchronous end-to-end.

8. Summary: what a strong answer covers

Clarified scope before designingJustified every number with a calculationNamed the fan-out amplification problem explicitly Designed a hybrid push/pull strategy, not just oneMade engagement counters asyncExplained why the cache stores IDs, not content
Interview tip The strongest signal in a Twitter design interview is naming the celebrity fan-out problem before the interviewer has to prompt for it, and then defending a concrete threshold (not "it depends") for when push flips to pull.
No comments
Leave a Comment