Design Twitter
Design Twitter step by step: requirements, scale, data model, hybrid fan-out, reliability, and the reasoning behind every major decision.
Page content
Twitter is a news-feed system.
Users publish short posts, follow other users, and open Home to see a combined stream of recent tweets. The main design question is:
How do we generate that home timeline quickly when some authors have hundreds of followers and others have tens of millions?
We will answer that question gradually. First we will define the product, estimate the traffic, and model the basic data. Then we will compare ways to generate the feed and let the workload lead us to the final architecture.
1. Clarify the problem
“Design Twitter” is too broad for one interview. I would begin with a few questions:
- Are we designing the chronological Following feed or the ranked For You feed?
- Are tweets text-only, or is media part of the core design?
- Do we need likes, replies, retweets, search, notifications, and trends?
- Are protected accounts and blocks in scope?
- Must new tweets appear live, or can users refresh?
- What traffic and retention should we support?
If the interviewer gives no additional constraints, I would state these assumptions:
Feed Reverse-chronological Following feed
Tweet content Text in the core design; media later
Users Logged in
Visibility Public and protected accounts
Delivery Refresh-based; a few seconds of feed lag is acceptable
Retention Keep tweet metadata until the author deletes it
This keeps the discussion focused on feed generation. Ranking, media, and search can be added after the core path works.
2. Functional requirements
The system must allow a user to:
- Publish and delete a tweet.
- Follow and unfollow another user.
- Read a home timeline containing tweets from followed accounts.
- Read a profile timeline containing tweets from one author.
- Fetch a single tweet by id.
- See only content they are authorized to view.
The initial design does not include:
- direct messages;
- ads and recommendations;
- full-text search;
- likes and retweets;
- notifications and trends; or
- video processing.
We will briefly explain how these fit at the end.
3. Non-functional requirements
The important quality targets are:
| Requirement | Target |
|---|---|
| Home latency | p99 below 200 ms |
| Post latency | p99 below 300 ms |
| Read availability | Approximately 99.99% |
| Durability | Never lose an acknowledged tweet |
| Author consistency | The author sees their own tweet immediately |
| Follower consistency | A delay of a few seconds is acceptable |
| Privacy | Never expose blocked or protected content incorrectly |
| Scalability | Handle skew from accounts with millions of followers |
Consistency is not one global setting.
For a new tweet:
Author's profile immediate
Follower's Home may lag by a few seconds
Tweet durability must be immediate
Privacy changes must be enforced before content is returned
Like/follower counters may lag
This distinction will allow us to move expensive feed delivery out of the synchronous post request without weakening durability or privacy.
Edge cases to keep in mind
- A celebrity publishes to 30 million followers.
- A mobile client retries after timing out.
- Alice follows Bob and immediately opens Home.
- Alice unfollows Bob while fan-out is still running.
- Bob deletes a tweet already present in millions of timelines.
- Fan-out workers receive duplicate or out-of-order events.
- Redis or Kafka becomes unavailable.
- Breaking news creates a sudden read spike.
4. Estimate the scale
Use round numbers. We need enough arithmetic to find the bottleneck.
Assume:
Daily active users 250 million
Home opens per user per day 5
New tweets per day 150 million
Average accounts followed 200
Peak multiplier 5×
Timeline page size 20 tweets
Home timeline traffic
250M users × 5 opens
= 1.25 billion Home requests/day
Average QPS
= 1.25B / 86,400
≈ 14,500 QPS
Peak QPS
≈ 14,500 × 5
≈ 72,500 QPS
Tweet creation traffic
150M tweets / 86,400
≈ 1,700 writes/second average
Peak
≈ 8,500 writes/second
The first useful conclusion is:
Feed reads greatly outnumber tweet writes.
That suggests doing more work when a tweet is created if it can make every later read cheaper.
Storage
If tweet metadata is roughly 300 bytes:
150M × 300 bytes
≈ 45 GB/day
≈ 16 TB/year before replication and indexes
This is manageable with partitioned storage. Media is far larger, so it will live in object storage.
5. What are we generating?
Suppose Alice follows Bob, Cara, and Dan.
Their recent tweets are:
Bob → B1, B2
Cara → C1
Dan → D1, D2
Ordered newest first, Alice’s Home might be:
B2
D2
C1
B1
D1
So the core query is:
Given Alice, return the newest tweets written by accounts Alice follows.
Everything else in the architecture exists to answer that query quickly and correctly.
6. APIs
Keep the API small and independent of the storage design.
Create a tweet
POST /v1/tweets
Authorization: Bearer <token>
Idempotency-Key: <uuid>
{
"text": "hello world",
"media_ids": []
}
HTTP 201 Created
{
"tweet_id": "t_9f3a",
"created_at": "2026-08-14T06:01:00Z"
}
The idempotency key is important. If the client times out after the server creates the tweet, retrying the same request should return the original tweet rather than create a duplicate.
Read Home
GET /v1/timelines/home?cursor=<opaque>&limit=20
HTTP 200 OK
{
"tweets": [
{
"tweet_id": "t_9f3a",
"author": {
"user_id": "u_bob",
"handle": "bob"
},
"text": "hello world",
"created_at": "2026-08-14T06:01:00Z"
}
],
"next_cursor": "eyJ0cyI6MTcyMzYxNTI2MCwiaWQiOiJ0XzlmM2EifQ"
}
Other core endpoints:
DELETE /v1/tweets/{tweet_id}
GET /v1/tweets/{tweet_id}
GET /v1/users/{user_id}/tweets?cursor=...&limit=20
POST /v1/users/{user_id}/follow
DELETE /v1/users/{user_id}/follow
GET /v1/users/{user_id}/followers?cursor=...
GET /v1/users/{user_id}/following?cursor=...
7. Basic data model
We need three logical datasets.
User
User
----
user_id
handle
display_name
is_protected
created_at
Tweet
Tweet
-----
tweet_id
author_id
text
media_ids
created_at
deleted_at
The main lookups are:
tweet_id → one tweet
author_id → recent tweets by that author
Follow
Follow
------
follower_id
followee_id
created_at
If Alice follows Bob:
Alice → Bob
We need both directions:
Who does Alice follow? → used when Alice reads Home
Who follows Bob? → used when Bob publishes
These are direct one-hop lookups, so a graph database is not necessary.
Tweets and follow relationships are facts we cannot lose. A generated home timeline is different: it is a shortcut that can be rebuilt from those facts.
Source of truth Tweets, follows, privacy settings
Derived data Profile timelines, home timelines, counters
Cache Hot tweet objects and timelines
Event system Carries changes to background consumers
We will choose actual databases after understanding how the feed is generated.
8. The central decision: how should we generate Home?
There are two basic approaches:
Fan-out on read Build the feed when Alice opens Home
Fan-out on write Prepare Alice's feed when followed users publish
Neither is always better. We need to compare their cost against our workload.
9. Approach 1: fan-out on read
When Alice opens Home:
Alice
│
▼
Load accounts Alice follows
│
▼
Load recent tweets from each account
│
▼
Merge by creation time
│
▼
Return the newest 20
If Alice follows 200 accounts, the service may need to read and merge 200 small timelines on every refresh.
Advantages
- Creating a tweet is cheap.
- Celebrity posts cost no more than ordinary posts.
- We do not store a separate home timeline for every user.
Disadvantages
- Read cost grows with following count.
- The same merge is repeated on every refresh.
- It is difficult to keep p99 below 200 ms at 72,500 peak QPS.
This is a good design for an early product. It is also a useful fallback when precomputed feeds are unavailable. At Twitter scale, it makes the dominant read path too expensive.
10. Approach 2: fan-out on write
Instead of waiting for Alice to read, prepare her timeline when Bob posts.
Bob creates tweet B3
│
▼
Find Bob's followers
│
▼
Add B3 to each follower's timeline
│
▼
Alice's Home already contains B3
Alice’s feed can now be stored as an ordered list of tweet ids:
home:alice
[B3, D2, C1, B2, D1, ...]
Reading Home becomes:
Alice
│
▼
Read top 20 tweet ids
│
▼
Fetch 20 tweet objects
│
▼
Return
This is much cheaper than merging hundreds of author timelines for every read.
Advantages
- Home reads are fast and predictable.
- We perform work once per follower instead of once per refresh.
- Cursor pagination is straightforward.
Disadvantages
- Every tweet creates many timeline writes.
- Inactive users receive updates they may never read.
- Authors with millions of followers create enormous fan-out jobs.
11. The celebrity problem
With an average of 200 followers:
150M tweets × 200 followers
= 30 billion timeline writes/day
≈ 347,000 writes/second average
This can be distributed across many workers.
Now suppose a celebrity has 30 million followers:
1 tweet
↓
30 million timeline writes
Even at 100,000 writes per second, that one fan-out takes about five minutes.
We should not make the author wait, and we should not let one celebrity tweet delay every ordinary tweet behind it.
Pure fan-out on write solves the common case but behaves badly for the highest-degree users.
12. The practical answer: hybrid fan-out
Use both models.
Ordinary author → fan-out on write
Celebrity author → fan-out on read
For example:
Bob has 800 followers
→ push Bob's tweet into follower timelines
Celeb has 30M followers
→ store the tweet only in Celeb's profile timeline
When Alice opens Home:
Alice's precomputed timeline
+
Recent tweets from celebrities Alice follows
│
▼
Merge by time
│
▼
Return top 20
Most users follow only a few very large accounts, so this merge is far cheaper than merging every followed account.
We might start with a threshold such as 5,000 followers:
followers < 5,000 push
followers ≥ 5,000 pull
The threshold should not be a permanent hardcoded product rule. Tune it using:
- active follower count;
- fan-out completion time;
- worker capacity;
- feed-read frequency; and
- storage cost.
The hybrid model is more complex, but it handles both the common case and the celebrity case efficiently.
13. High-level architecture
Now we have enough information to draw the main components.
┌──────────────┐
│ Clients │
└──────┬───────┘
│
┌──────▼───────┐
│ API Gateway │
│ Auth + limits│
└───┬────┬─────┘
│ │
┌─────────────┘ └──────────────┐
▼ ▼
┌─────────────┐ ┌──────────────┐
│Tweet Service│ │Timeline Svc. │
└──────┬──────┘ └───┬──────┬───┘
│ │ │
┌──────▼──────┐ ┌────▼───┐ │
│ Tweet Store │ │ Feed │ │
│ + Outbox │ │ Store │ │
└──────┬──────┘ └────────┘ │
│ │
▼ ▼
Kafka Tweet Cache/DB
│
▼
Fan-out Workers ◄──────── Graph Service ─────► Follow DB
│
▼
Feed Store
Why each component exists
- Tweet Service: validates and durably stores tweets.
- Graph Service: answers following and follower lookups.
- Timeline Service: assembles a page of Home.
- Feed Store: holds precomputed tweet ids for active users.
- Kafka: separates tweet creation from asynchronous fan-out.
- Fan-out workers: perform the high-volume timeline writes.
- Tweet cache: prevents every timeline read from hitting the Tweet Store.
Posting flow
- The client sends a tweet to the Tweet Service.
- The service stores it durably.
- It publishes an event through the outbox.
- The API returns without waiting for fan-out.
- Workers consume the event, load followers, and update feed entries.
Home flow
- The Timeline Service reads precomputed tweet ids.
- It also reads recent tweets from celebrity followees.
- It merges and paginates the candidates.
- It fetches complete tweet objects.
- It applies current visibility rules.
- It returns the page.
14. Posting a tweet
The write path should be:
Authenticate and rate-limit
↓
Validate text and idempotency key
↓
Generate tweet_id
↓
Store tweet durably
↓
Append to author's profile timeline
↓
Warm tweet cache
↓
Create fan-out event
↓
Return 201
The tweet record is the source of truth. The author’s profile should show it immediately.
Follower delivery is asynchronous. Post latency therefore does not depend on whether the author has 50 followers or 50 million.
15. Why Kafka?
Without a queue:
POST /tweets
↓
Store tweet
↓
Find followers
↓
Write thousands of timelines
↓
Return response
Post latency would grow with follower count, and a traffic spike could exhaust request threads.
With Kafka:
POST /tweets
↓
Store tweet
↓
Publish event
↓
Return
Kafka → workers → feed entries
Kafka solves four problems:
- It decouples post latency from fan-out work.
- It absorbs short bursts.
- It allows fan-out, search, and notifications to consume the same event independently.
- It retains events so consumers can retry or rebuild derived data.
A managed queue such as SQS or Pub/Sub is a valid alternative if we mainly need task delivery. Kafka is attractive when replay, multiple consumers, measurable lag, and partition ordering matter. Its trade-off is operational complexity.
Avoid the dual-write problem
This sequence is unsafe:
Database write succeeds
Kafka publish fails
The tweet would exist but never reach followers.
Use a transactional outbox:
One database transaction
├── insert tweet
└── insert outbox event
Outbox relay
└── publish event to Kafka
If Kafka is unavailable, the outbox record remains and the relay retries later. The tweet is delayed, not lost.
16. Fan-out workers
A worker should not load millions of followers into memory.
Instead:
TweetCreated
↓
Read follower count
↓
Ordinary author?
├── no → stop; tweet will be pulled on read
└── yes → create follower-page jobs
↓
500 followers/job
↓
write feed entries
Small jobs can run in parallel and retry independently.
Partition tweet events by author_id. This preserves ordering for one author’s tweets while allowing different authors to process concurrently. We do not need one global order across all tweets.
Kafka commonly delivers at least once, so duplicate events are expected. Make the feed write idempotent by using tweet_id as the unique member. Adding the same tweet twice has the same result as adding it once.
If consumers fall behind:
- scale workers based on consumer lag;
- cap concurrency for unusually large authors;
- prioritize recent events;
- retain events until consumers recover; and
- temporarily pull recent profile tweets during Home reads.
That last fallback helps users see fresh tweets while fan-out catches up.
17. Reading the feed
The Feed Store contains ids, not complete tweets:
home:alice
[t_100, t_98, t_92, t_87]
The read path is:
Read candidate tweet ids
↓
Add celebrity candidates
↓
Merge by (created_at, tweet_id)
↓
Fetch tweet objects in one batch
↓
Filter deleted and unauthorized tweets
↓
Return 20 results
Fetching full objects from ids is often called hydration.
Why not copy full tweet JSON into every feed?
Suppose Bob deletes a tweet copied into ten million timelines. If those timelines contain the full object, ten million copies become stale. If they contain only an id, we delete one canonical Tweet record, invalidate one cache entry, and let hydration skip stale ids.
If filtering removes five candidates, the Timeline Service fetches more ids until it fills the page or reaches the end.
18. Follow and unfollow
Follow
Suppose Alice follows Bob.
We can choose:
Option A Show only tweets Bob creates from now on
Option B Backfill Bob's recent tweets into Alice's feed
Most products choose some form of backfill:
Alice follows Bob
↓
Store follow relationship
↓
Fetch Bob's latest N tweet ids
↓
Add them to Alice's feed
Backfill is asynchronous. Until it completes, the read path can also pull Bob’s recent profile tweets so Home does not appear broken immediately after Follow.
Unfollow
When Alice unfollows Bob, do not scan and rewrite her entire timeline synchronously.
Remove follow relationship
↓
Filter Bob's stale candidates during reads
↓
Clean old feed entries asynchronously
The follow record is authoritative. The feed is only derived data.
Blocks and protected accounts use the same principle: verify current permission before returning the tweet.
19. Pagination
Do not use offset pagination for a changing feed.
With:
GET /home?offset=20
new tweets inserted above the current page move every offset. The user can see duplicates or skip entries.
Use a cursor containing the last item’s ordering fields:
cursor = (created_at, tweet_id)
The next request asks for entries older than that pair:
GET /v1/timelines/home?cursor=<encoded-pair>&limit=20
tweet_id breaks ties when two tweets have the same timestamp.
For a ranked feed, the cursor may instead contain a stable ranking score, tweet id, and feed/session version.
20. Storage choices
Choose storage from the access pattern.
Tweet Store
We need:
tweet_id → one tweet
author_id → recent tweets, newest first
A sharded relational database is a good starting point because it supports transactions and the outbox cleanly.
At larger scale, Cassandra or DynamoDB-style tables are also a strong fit:
tweets_by_id
Partition key: hash(tweet_id)
tweets_by_author
Partition key: (author_id, time_bucket)
Sort key: (created_at, tweet_id)
The time bucket prevents one author’s partition from growing forever.
The reason to choose a distributed KV or wide-column store is not simply “NoSQL scales.” It is that our reads are predictable key and ordered-range lookups, and we need horizontal write throughput. The trade-off is more denormalization and less transactional flexibility.
Follow Store
We need both adjacency directions:
following_by_user
Partition key: follower_id
followers_by_author
Partition key: (followee_id, bucket)
Bucket the follower side because one celebrity may have tens of millions of edges.
A relational store works early. A wide-column or KV store fits the large-scale access pattern. A graph database is unnecessary because we are not traversing several relationship hops.
Feed Store
We need:
user_id → recent ordered tweet ids
Redis sorted sets are useful for the hot portion:
Key home:{user_id}
Score timestamp or rank score
Member tweet_id
Keep perhaps the latest 500–800 ids and expire inactive users.
If losing Redis cannot be tolerated, also store timelines in Cassandra, DynamoDB, or another durable append-friendly store. If pull-based rebuilding is acceptable, Redis can remain an ephemeral serving layer. That is a recovery-time trade-off, not a universal rule.
21. Caching
Useful caches include:
| Cache | Key | Why |
|---|---|---|
| Tweet | tweet:{tweet_id} | Avoid fetching the same popular tweet repeatedly |
| User | user:{user_id} | Author details appear on every hydrated tweet |
| Following | following:{user_id} | Avoid repeated graph reads |
| Home | home:{user_id} | Serve recent feed ids quickly |
| Profile | profile:{user_id} | Celebrity timelines are read frequently |
TTL and invalidation
Tweet deleted invalidate tweet:{id}
Profile updated invalidate user:{id}
Follow changed invalidate following:{user_id}
Home timeline append/trim; expire inactive users
On a miss, read the source store and refill the cache.
Hot keys and stampedes
A viral tweet may receive millions of reads. Warm its cache during creation, replicate very hot entries if one node becomes network-bound, and coalesce simultaneous misses so only one request reaches the database.
Use TTL jitter to prevent many popular keys expiring at the same instant.
Redis accelerates reads. It is not the source of truth.
22. Partitioning and horizontal scaling
Partition each dataset using the field used to read it:
| Dataset | Partition key | Reason |
|---|---|---|
| Tweet by id | hash(tweet_id) | Spread current writes evenly |
| Author timeline | (author_id, time_bucket) | Read one author’s ordered tweets |
| Following | follower_id | Read one user’s followees together |
| Followers | (followee_id, bucket) | Spread celebrity follower lists |
| Home timeline | user_id | Read one user’s feed together |
| Kafka events | author_id | Preserve per-author ordering |
Stateless API services scale behind load balancers. Stateful stores scale by adding partitions.
Celebrity accounts still create hot keys even after general sharding. Give them additional follower buckets, cache replicas for profile reads, and isolated worker capacity so they cannot dominate ordinary traffic.
23. Consistency and correctness
Tweet creation
The Tweet record must be durably committed before the API returns. Fan-out may happen later.
Author’s profile
The author should see the tweet immediately. Update the profile timeline in the write path, or read recent self-authored tweets directly from the Tweet Store while the projection catches up.
Follower timelines
Eventual consistency is acceptable. The outbox guarantees that a committed tweet eventually produces an event.
Follow and unfollow
The Follow record changes before success is returned. Cached and precomputed timelines may lag, so read-time filtering uses current graph state.
Protected accounts and blocks
Never rely on feed membership as authorization. If permission cannot be verified, fail closed for protected content.
Deletes and out-of-order events
Publish a version or event timestamp with create and delete events. Keep a deletion tombstone so a delayed TweetCreated event cannot resurrect a deleted tweet in a derived store.
Counters
Follower and like counts can be eventually consistent. Reconcile them periodically from source records. Never use an approximate counter as the only authorization check.
24. Failure scenarios
Kafka is unavailable
The Tweet Service still commits the tweet and outbox record. The relay retries when Kafka recovers. Followers see a delay, but no tweet is lost.
Alert on:
oldest unpublished outbox record
outbox backlog size
Kafka publish failure rate
Feed Store or Redis is unavailable
Possible degraded behavior:
Durable feed store available → serve from it
No durable feed store → bounded fan-out on read
Database under pressure → return a stale cached page
Use admission control so fallback traffic does not turn a Redis outage into a Tweet Store outage.
Fan-out workers fall behind
Kafka retains events. Scale workers based on lag, not CPU alone. Home can merge recent author timelines while the backlog drains.
A dependency becomes slow
Use:
- short, explicit timeouts;
- bounded retries with exponential backoff and jitter;
- circuit breakers;
- separate connection pools for critical and optional work; and
- partial responses when safe.
Unlimited retries create a retry storm and spread one dependency’s failure through the system.
Traffic suddenly spikes
- Serve cached tweets and profiles.
- Autoscale stateless services and consumers.
- Rate-limit tweet creation and excessive refreshes.
- Delay nonessential counters and analytics.
- Isolate celebrity workloads.
Duplicate delivery
At-least-once delivery means duplicates are normal. Feed writes, deletes, and notification aggregation must be idempotent.
25. Security and abuse prevention
Authentication
The API Gateway validates a session or OAuth token. Internal services authenticate using workload identities or mTLS.
Authorization
The Tweet Service verifies ownership before deletion. The Timeline Service checks protected-account, block, and visibility rules before returning content.
Rate limiting
Use token buckets by user and IP for:
- tweet creation;
- follow/unfollow actions;
- timeline refreshes;
- login attempts; and
- media uploads.
For sensitive writes, use conservative local limits if the distributed limiter is unavailable. For reads, limited fail-open behavior may preserve availability.
Data protection and abuse
Encrypt data in transit and at rest. Do not log access tokens or tweet text unnecessarily. Apply stricter limits to accounts showing spam, automated follow churn, or duplicate posting patterns.
Abuse is a scaling problem too: malicious clients can generate more load than normal users.
26. Observability
Monitor the user journey, not only servers.
Important metrics
- Home and post request rate, errors, and p50/p95/p99 latency;
- Tweet Store commit latency and replication lag;
- Redis hit rate, eviction rate, memory, and hot-key traffic;
- Kafka publish errors, consumer lag, and oldest-event age;
- fan-out writes per second and completion time;
- hydration cache misses and filtered-candidate rate;
- outbox backlog and oldest unpublished record; and
- regional replication lag.
The most valuable end-to-end metric is:
Time from durable tweet creation until an eligible follower can retrieve it.
Call this tweet visibility lag. A healthy Kafka lag does not guarantee healthy timeline writes, so measure the complete path.
Logs and tracing
Use structured logs with request id, tweet id, event id, region, dependency latency, and retry reason. Do not log sensitive content by default.
Propagate a trace id into the outbox event and consumer. This lets us see where time was spent across a request and its later asynchronous work.
Alerts
Alert on SLO symptoms:
- Home p99 above 200 ms;
- tweet commit failures;
- visibility lag above the allowed window;
- growing outbox or Kafka lag;
- Redis memory pressure;
- hot partitions;
- unusual authorization failures; and
- regional replication problems.
27. Multi-region design
Assign each user a home region.
Global traffic manager
┌──────┴──────┐
│ │
Region A Region B
Alice's home Bob's home
│ │
└── asynchronous ──┘
replication
When Bob posts:
- Route the write to Bob’s home region.
- Commit the tweet there.
- Publish the event locally.
- Replicate the tweet and event to other regions.
- Regional workers update local follower timelines.
Alice reads Home from her own region. If Bob lives elsewhere, his tweet may take slightly longer to arrive. That fits our follower-consistency requirement.
During a regional outage, serve replicated reads from another region. Before accepting writes there, fence the failed region so both regions cannot act as the writer for the same user. A region lease or epoch can establish one active owner.
Media uses globally replicated object storage and a CDN. Private account data may remain in a required geography for data-residency reasons.
28. Adding the remaining features
Media
Client
↓ request pre-signed URL
Object Storage
↓
Scan/transcode workers
↓
CDN
The Tweet record contains media ids, not image or video bytes.
Likes and retweets
Store (user_id, tweet_id) as the source of truth for likes. Keep a fast counter in Redis and reconcile it periodically.
A retweet can be a new tweet that references the original and uses the same delivery pipeline.
Search
Consume tweet create and delete events into an inverted index such as OpenSearch. Search can lag by a few seconds without affecting tweet durability.
Notifications
Consume follow, like, reply, and mention events. Aggregate repetitive events before sending through APNs or FCM.
Ranking
Keep candidate generation separate from ranking:
Precomputed feed + celebrity tweets
↓
Candidates
↓
Ranking Service
↓
Top 20
A score might combine recency, engagement, and author affinity. Keeping this layer separate allows the model to change without redesigning tweet storage.
Live updates
Polling is simplest. If the product requires a “new tweets available” signal, Server-Sent Events are often enough because communication is one-way.
WebSockets add bidirectional messaging but also connection routing, heartbeats, reconnection, and backpressure. They do not replace the timeline API or solve feed fan-out by themselves.
29. Final architecture
WRITE
─────
Client
→ API Gateway
→ Tweet Service
→ Tweet Store + Outbox
→ Return 201
→ Kafka
→ Fan-out Workers
→ Home Feed Store
READ
────
Client
→ API Gateway
→ Timeline Service
→ Precomputed Feed
+ Celebrity Timelines
→ Merge
→ Fetch Tweet Objects
→ Check Visibility
→ Return Page
The main reasoning chain is:
Home must be fast
↓
Reads greatly outnumber writes
↓
Precompute feeds for ordinary authors
↓
Celebrity fan-out creates extreme write amplification
↓
Pull celebrity tweets during reads
↓
Use a hybrid feed
Kafka makes fan-out asynchronous. Redis serves hot feed and tweet data. Tweets and follow records remain the source of truth. Timelines are derived and rebuildable.
30. Interview-ready summary
Key decisions to remember
- Start with the simple pull model before introducing infrastructure.
- Push ordinary-author tweets because Home is read-heavy.
- Pull celebrity tweets to avoid millions of writes from one post.
- Store tweet ids in feeds, then hydrate complete objects.
- Commit the tweet before acknowledging it.
- Use an outbox so a committed tweet cannot miss its Kafka event.
- Make fan-out idempotent because duplicate delivery is expected.
- Check current authorization during reads.
- Partition follower lists to handle high-degree accounts.
- Measure end-to-end tweet visibility lag.
Likely interviewer follow-up questions
- How would you choose the celebrity threshold?
- What happens when a user follows someone and immediately opens Home?
- How do you delete a tweet from millions of feeds?
- What happens when Kafka or Redis is unavailable?
- How do you prevent duplicate tweets after a retry?
- How do you preserve ordering?
- How would you add ranking?
- How would you support a user following 50,000 accounts?
- How do protected accounts work with stale feed entries?
- How would you fail over a region?
Senior-level points that differentiate the answer
- Explain where eventual consistency is acceptable and where it is unsafe.
- Distinguish source-of-truth records from derived feeds and caches.
- Identify and solve the database/Kafka dual-write problem.
- Treat duplicate and out-of-order events as normal.
- Describe backpressure and a degraded read path when consumers lag.
- Discuss hot keys and celebrity-specific isolation.
- Fail closed when private visibility cannot be verified.
- Monitor visibility lag rather than only queue lag.
A 1–2 minute verbal answer
I would scope Twitter to posting, following, profile timelines, and a reverse-chronological Following feed. At 250 million daily users and five Home loads per day, we have roughly 14,500 average and 72,500 peak feed reads per second, while tweet creation averages only about 1,700 writes per second. Since reads dominate, I want to precompute feeds.
Pure fan-out on write makes Home fast, but it creates tens of millions of writes for a celebrity tweet. I would therefore use hybrid fan-out: push tweet ids into follower feeds for ordinary authors, skip that work for celebrity authors, and merge their recent profile tweets during reads.
The Tweet Service durably stores the tweet and an outbox record before returning. An outbox relay publishes to Kafka, and idempotent fan-out workers update derived feeds asynchronously. Home reads a user’s precomputed ids, adds celebrity candidates, cursor-paginates, batch-fetches tweet objects, and enforces current privacy rules.
Tweets and follow edges are sources of truth; feeds and caches are rebuildable. I would partition tweets by hash of tweet id, author timelines by author and time bucket, and celebrity follower lists by author plus bucket. If Kafka is down, the outbox waits. If Redis is down, Home degrades to a durable feed store or bounded fan-out on read. The key operational metric is end-to-end tweet visibility lag.
For the broader interview framework around this problem, see the System Design Interview Complete Guide.
