Start with the product, not the diagram
A Twitter clone is a canonical system design question because it looks simple and then exposes almost every backend tradeoff: fanout, hot users, timelines, storage, ranking, caching, rate limits, moderation and observability. The interview is rarely about inventing Twitter. It is about showing that you can move from product requirements to a defensible architecture and then defend that architecture under pressure.
A good first answer is not a diagram. It is a requirements negotiation:
- Users can post short text updates.
- Users can follow other users.
- Users can view a home timeline from followed accounts.
- Users can view a profile timeline for one account.
- Users can like, repost and reply.
- The system should handle high read traffic and uneven write traffic.
- Timeline freshness matters, but perfect ordering is not required for every user.
Then set explicit non-goals for a 45-minute interview: ads, direct messages, advanced recommendation ranking, full-text search and complex trust-and-safety workflows. You can mention that real systems need those, but do not let them swallow the design.
Before you draw anything, put numbers on the table. Scale assumptions are what turn a vague answer into an engineering one. A reasonable set to propose out loud:
| Quantity | Assumption | Why it matters |
|---|---|---|
| Daily active users | 200 million | Sets read and write throughput |
| Posts per day | 400 million | Drives write path and storage growth |
| Average post writes/sec | ~5,000 | Baseline for the post service |
| Peak post writes/sec | ~25,000 | Headroom you must design for |
| Timeline reads/sec | ~300,000 | Read-heavy by roughly 50:1 |
| Average followers | ~200 | Sets typical fanout cost |
| Largest accounts | 100M+ followers | Forces the hybrid strategy |
You do not need these to be Twitter's real figures. You need them to be internally consistent, so that when you say "reads dominate writes by about fifty to one" the rest of your design follows from that ratio. State the assumption, then let the interviewer correct it. That single move signals seniority more than any buzzword.
This format matters in 2026 because system design is appearing earlier in interview loops, not only at staff level. The bar has risen across the board: The Pragmatic Engineer's account of tech interviews describes the difficulty shifting roughly a full standard deviation higher than the 2020 to 2022 hiring window, which means more architecture probing even for roles that used to focus mostly on coding. The signal interviewers want is production judgement, not memorised diagrams.
Define the core model and API
Keep the model small. You need users, posts, follows and timelines. Everything else is optional.
type User = {
id: string;
handle: string;
displayName: string;
createdAt: string;
};
type Post = {
id: string;
authorId: string;
body: string;
createdAt: string;
replyToPostId?: string;
};
type Follow = {
followerId: string;
followeeId: string;
createdAt: string;
};
type TimelineItem = {
postId: string;
authorId: string;
createdAt: string;
score?: number;
};The API can be boring. Boring is good in system design:
POST /v1/posts
GET /v1/users/{handle}/posts?cursor=...
POST /v1/users/{id}/follow
DELETE /v1/users/{id}/follow
GET /v1/timeline/home?cursor=...
GET /v1/posts/{id}/replies?cursor=...Two design decisions are worth flagging while you write that out. First, use cursor pagination, not offset pagination. Offset pagination (?page=5) breaks the moment new posts arrive at the head of the feed, because every row shifts down by one. A cursor encodes a stable position, usually the createdAt plus postId of the last item seen, so the next page is correct even under heavy write activity. Second, generate post IDs as time-sortable identifiers (Snowflake-style: a timestamp prefix plus a machine and sequence suffix). That gives you globally unique IDs, rough chronological ordering for free, and a natural sharding key, without a central counter on the write path.
In a real interview, state the consistency you need for each operation. Spelling this out prevents the interviewer from assuming you want strong consistency everywhere, which would wreck your read path:
| Operation | Consistency target | Reasoning |
|---|---|---|
| Create post | Durable before 200 OK | Users must not lose posts |
| Home timeline read | Eventually consistent | A few seconds of lag is fine |
| Follow / unfollow | Fast, read-your-writes | Users expect their own action to stick |
| Like / repost counts | Eventually consistent | Approximate counts are acceptable |
This is where many candidates overcomplicate the answer. You do not need a graph database for a basic follow graph. A relational database or wide-column store can hold follows. The hard part is read fanout, not storing an edge.
Choose fanout on write, fanout on read, or a hybrid
The core Twitter design decision is timeline construction.
Fanout on read means you store posts once, and when a user opens their home timeline you fetch recent posts from all followed accounts, merge them and rank them. This works for users following a small number of accounts. It becomes expensive for users following thousands of accounts, and it makes every timeline request do a lot of work. It is sometimes called the "pull" model, because reads pull from authors on demand.
Fanout on write means that when Alice posts, you push a reference to that post into each follower's home timeline. Reads become cheap because the timeline is precomputed. Writes become expensive for authors with many followers. This is the "push" model, because the writer pushes into reader inboxes.
The two approaches sit at opposite ends of a cost spectrum:
| Dimension | Fanout on write (push) | Fanout on read (pull) |
|---|---|---|
| Read cost | Cheap, one lookup | Expensive, merge N authors |
| Write cost | Expensive for big accounts | Cheap, one insert |
| Timeline freshness | Slight lag from queue | Fresh at read time |
| Storage | High, duplicated per follower | Low, posts stored once |
| Best for | Normal accounts | Celebrity accounts |
Most strong answers choose a hybrid:
- Normal authors use fanout on write into follower timeline stores.
- Very large accounts use fanout on read or delayed fanout.
- The timeline service merges precomputed items with recent posts from large accounts.
- Caches absorb hot timelines and hot posts.
That hybrid is realistic because social graphs are skewed. A tiny fraction of accounts cause a huge fraction of fanout. The architecture should admit that instead of pretending every user has the same load.
The skew is the whole problem. If you push a post from an account with 100 million followers, you have just generated 100 million writes from a single tweet. That is why celebrity accounts are pulled at read time and merged in, rather than fanned out on write.
const CELEBRITY_FOLLOWER_THRESHOLD = 1_000_000;
export function chooseFanoutStrategy(followerCount: number) {
if (followerCount >= CELEBRITY_FOLLOWER_THRESHOLD) {
return "fanout_on_read";
}
return "fanout_on_write";
}That code is intentionally small. It shows the decision boundary. In production you would use config, experiments and load metrics rather than a hard-coded threshold, and you would likely have a middle tier: accounts large enough to be expensive but not large enough to pull, where you fan out on a delay or only to recently active followers. Fanning out only to active followers is a common optimisation, because most followers of a large account will not open the app before the next post arrives anyway.
For background that focuses on real bottlenecks rather than trivia, the GeeksforGeeks walkthrough of designing Twitter lays out the same push, pull, and hybrid trichotomy, and HackerRank's argument for testing real-world development skills explains why interviewers increasingly probe tradeoffs after the first diagram rather than scoring the diagram itself.
A worked example: tracing one post end to end
Abstract diagrams hide the interesting decisions. Walk the interviewer through a single concrete request instead. Suppose Alice has 800 followers and posts "shipping the new release today".
- The client calls
POST /v1/posts. The post service validates the body, allocates a Snowflake ID, and writes the row to the post store. It does not wait for fanout. The moment the write is durable, it returns201 Createdwith the post ID. This keeps the write path fast and predictable. - The post service publishes a
FanoutJobto a queue. The user is already done; everything after this is asynchronous. - A fanout worker picks up the job, looks up Alice's follower count, and sees 800, well under the celebrity threshold. It chooses fanout on write.
- The worker reads Alice's followers in batches and inserts a compact timeline record into each follower's home timeline store, keyed by
(userId, postId)so retries are safe. - Minutes later, Bob (a follower) opens the app. The timeline service reads Bob's precomputed timeline records, which are already sorted by time. It also pulls recent posts from the handful of celebrity accounts Bob follows, since those were never pushed.
- The service merges the precomputed records with the celebrity posts, applies any ranking, hydrates the post bodies in a batch, filters out anything deleted or blocked, and returns the page.
Now change one variable: Alice has 60 million followers. Step 3 flips to fanout on read. Nothing is pushed. Her post is stored once. When Bob opens his timeline in step 5, Alice's recent posts are pulled and merged at that moment. Same machinery, different branch. Being able to narrate both branches from the same diagram is what separates a confident answer from a memorised one.
Storage and caching choices
A reasonable storage split:
- User service: relational database for user profiles and account metadata.
- Social graph service: relational or wide-column store keyed by follower and followee.
- Post service: durable store for posts, partitioned by author or time.
- Timeline service: key-value or wide-column store for per-user home timeline item IDs.
- Cache: Redis or Memcached for hot timelines, user profiles and post payloads.
- Search: separate index for text search, not in scope for the core timeline.
For timeline storage, you can store compact references rather than full posts:
type HomeTimelineRecord = {
userId: string;
postId: string;
authorId: string;
createdAtMs: number;
};This lets you hydrate posts in batches:
export async function hydrateTimeline(
records: HomeTimelineRecord[],
loadPosts: (postIds: string[]) => Promise<Post[]>,
) {
const posts = await loadPosts(records.map((record) => record.postId));
const byId = new Map(posts.map((post) => [post.id, post]));
return records
.map((record) => byId.get(record.postId))
.filter((post): post is Post => Boolean(post));
}Storing references rather than full copies matters at scale. If every follower's timeline held a full copy of each post body, a single edit or deletion would have to chase down millions of duplicates, and storage would balloon. Holding only IDs means the post body lives in one place, edits are cheap, and you trade a little read-time work (the hydration step) for a lot of write-time and storage savings. Discord's writeup of how it stores billions of messages is a useful real-world anchor here: it leans on Snowflake-style IDs as the partition and sort key in exactly the way this design uses them for posts, and it is candid about the eventual-consistency edge cases that show up when you partition at that scale.
Caching deserves its own thought, not a hand-wave. The home timeline of an active user is the hottest object in the system, so cache the materialised page and bound it: keep only the most recent few hundred entries per user, since almost nobody scrolls a thousand posts deep. Cache post payloads separately, keyed by post ID, so the same hot post serves many timelines from one cache entry. Be explicit about the failure mode too. On a cache miss you fall back to the timeline store; if you ever see a thundering herd where many requests miss the same key at once, use a single-flight lock or request coalescing so only one rebuild runs while the others wait.
Do not spend the whole interview naming databases. The important part is access pattern:
- "Give me the latest home timeline item IDs for user X."
- "Give me the latest posts by author Y."
- "Give me all followers of author Y in batches."
- "Check whether user A follows user B."
If you can explain the access pattern, the database choice becomes grounded. Note the last two are different shapes: "followers of Y" is a fan-out read used during write-time fanout, while "does A follow B" is a point lookup used to render a profile button. A social graph store usually needs both directions indexed, which is a detail strong candidates raise unprompted.
Reliability, abuse and observability
A production social system is not only a timeline. It needs guardrails:
- Rate limit post creation, follows, likes and replies.
- Detect spammy follow bursts and duplicate content.
- Put fanout work on queues so posting does not block on every follower write.
- Retry failed fanout jobs with idempotency keys.
- Track lag between post creation and follower timeline visibility.
- Track cache hit rate, timeline p95 latency, queue depth and failed hydrations.
The queue is central:
type FanoutJob = {
jobId: string;
postId: string;
authorId: string;
followerBatchCursor?: string;
createdAt: string;
};Make fanout idempotent by writing timeline records with a natural key such as (userId, postId). If a job retries, it should not duplicate posts in timelines. The followerBatchCursor is doing quiet but important work: a post from a large account cannot be fanned out in one job without risking a timeout, so the worker processes a batch, advances the cursor, and re-enqueues the remainder. That makes each unit of work small, retryable and observable.
Decide what happens when a fanout job exhausts its retries. The answer is a dead-letter queue plus an alert, never a silent drop. A post that fails to reach some followers is a correctness bug a user will eventually notice, so it needs a path to investigation rather than disappearing.
Moderation is also worth naming. If a post is deleted or restricted, timeline hydration must respect that. You can either remove timeline references asynchronously or filter at read time. Filtering at read time is safer but adds read cost. Removing references is cleaner but can lag. A mature answer names that tradeoff and picks one: filter at read time for anything legally or safety critical, where a stale reference is unacceptable, and clean up references lazily in the background for ordinary deletions.
On observability, give yourself a small set of numbers that would tell you the system is healthy. The single most diagnostic one is fanout lag, the time between a post being created and it appearing in follower timelines. If that p95 climbs, your queue is backing up before users complain. Pair it with timeline read p95, cache hit rate and queue depth, and you have a dashboard that explains most incidents.
What the celebrity-skew answer reveals about your level
Because this question is so common, interviewers stopped scoring whether you can draw the boxes years ago. They score how you handle the one hard part: the skew. The same Twitter prompt sorts candidates by how far they push the fanout decision, and the celebrity-account branch is the cleanest tell.
| Level | Where the candidate lands on fanout |
|---|---|
| Mid-level | Produces a clean push-based design with batched hydration; needs a nudge to see why a 100M-follower account breaks it |
| Senior | Reaches the hybrid model unprompted; talks about idempotency, fanout lag and active-follower filtering without being asked |
| Staff and above | Questions whether the celebrity tier is even worth building now, reasons about cost and migration, and splits the design across team boundaries |
A mid-level candidate who stops at "fan out on write to every follower" has given a correct answer that does not survive its own scale numbers. A senior candidate names the skew, picks the hybrid, and explains how they would measure whether the threshold is set right. A staff candidate goes further and challenges the brief itself, deciding which parts are not worth the engineering cost until the product proves it needs them.
The other dimension is which slice of the system you go deep on, and that is driven by the role rather than the level. A backend engineer should mine the data model and the write path. A platform or infrastructure engineer should dwell on queues, partitioning, capacity, and the failure modes. An ML-leaning candidate is expected to say something credible about timeline ranking even though ranking is a non-goal for the core build. Read which one the interviewer leans toward and spend your minutes there rather than narrating all of it evenly.
How to present the design in an interview
Use this order:
- Clarify requirements and scale assumptions.
- Define core entities and APIs.
- Choose timeline strategy and explain fanout tradeoffs.
- Describe storage by access pattern.
- Add caching, queues and hot-user handling.
- Cover reliability, abuse and observability.
- State what you would improve with more time.
Avoid these common mistakes:
- Drawing microservices before requirements.
- Treating "use Kafka" as an explanation rather than naming what it buys you.
- Ignoring celebrity accounts and uneven traffic.
- Going silent and designing in your head; interviewers grade your reasoning, so narrate it.
- Reaching for strong consistency everywhere, which quietly destroys the read path.
What weak versus strong looks like in practice: a weak answer says "I'll store posts in a database and load the timeline when the user opens the app." A strong answer says "reads dominate writes by about fifty to one, so I'll precompute timelines with fanout on write for normal accounts, fall back to fanout on read for the small number of celebrity accounts whose posts would generate millions of writes, and merge the two at read time." Same components, but the second answer is anchored to numbers and names the tradeoff.
The strongest candidates sound pragmatic. They do not claim perfect freshness, perfect ordering and cheap writes at global scale. They make tradeoffs and then explain how they would measure whether those tradeoffs work.
FAQ
Do I need to memorise exact numbers like Twitter's real QPS? No. You need self-consistent estimates and the ability to derive load from them. State your assumptions, do rough arithmetic out loud, and adjust when the interviewer pushes back.
Should I propose a graph database for the follow graph? Usually not for a 45-minute design. A relational or wide-column store with both follower and followee directions indexed is enough. A graph database is justified only if the question expands into multi-hop recommendations, which is typically a non-goal.
How do I handle timeline ranking? Treat it as a separate, later concern. Build the chronological merge first, then mention that a scoring step (recency, engagement, affinity) can be layered on the merged candidate set. Do not let ranking eat your time budget unless the role is explicitly ML focused.
What if I run out of time? Get to a complete, working design quickly, then deepen. A coherent end-to-end answer that names its gaps beats a beautifully detailed write path with no timeline read. Finish the skeleton, then add muscle where the interviewer leans in.
Practise the next design
Once the Twitter timeline clicks, the same fanout-and-skew muscle transfers to almost every social-feed design:
- System design cheat sheet for the reusable estimation and storage patterns under time pressure.
- Backend system design deep dive for the write-path and consistency reasoning at more depth.
- Senior system design interview prep for how staff-level interviewers push on scope and tradeoffs.
- Backend engineer interview questions for how these tradeoffs surface across a full loop.
Sources
- The Pragmatic Engineer, The Reality of Tech Interviews, on the rising difficulty bar that pushed system design earlier in loops.
- Discord Engineering, How Discord Stores Billions of Messages, a primary account of Snowflake-ID partitioning and eventual-consistency tradeoffs at scale.
- GeeksforGeeks, Designing Twitter: A System Design Interview Question, on the push, pull, and hybrid fanout trichotomy.
- HackerRank, Testing Real-World Development Skills, on why interviews probe tradeoffs rather than the first diagram.