System Design Guide

Practice · Question 4 · Hard

Design a news feed (like Twitter / X)

Users post, follow others and read a home timeline. The classic fan-out problem, including the celebrity case that breaks the simple solution.

5 min read · 4 flashcards

1. Clarify requirements

Functional

  • Post a tweet (text; media via object storage + CDN).
  • Follow and unfollow users.
  • Home timeline: recent tweets from followed users, newest first (ranking is an extension).
  • User timeline: one user’s own tweets.

Non-functional

  • Fast timeline loads (p99 < 200 ms).
  • Eventual consistency is fine: a new tweet can take a few seconds to appear for followers.
  • Highly available. Read-heavy.
  • Scale: 200M DAU; each loads the timeline ~10× a day and posts ~0.5 tweets a day; average 200 followers, but some accounts have 100M+.

2. Estimates

  • Timeline reads: 200M × 10 = 2B/day → ~20k/s average, ~50k/s peak.
  • Tweets: 200M × 0.5 = 100M/day → ~1.2k/s.
  • Fan-out on write: 1.2k tweets/s × 200 followers ≈ 240k timeline inserts/s on average. Manageable with a queue and workers.
  • Celebrity problem: one tweet by an account with 100M followers = 100M inserts for a single post. That’s the design challenge.
  • Timeline cache: keep the latest ~800 tweet IDs per active user: 8 bytes × 800 ≈ 6.4 KB × 200M ≈ 1.3 TB of Redis, spread across a cluster. Feasible, especially if we only cache active users.

3. API

POST /v1/tweets           { text, media_ids[] }           → { tweet_id }
GET  /v1/timeline/home?cursor=…&limit=20                  → { tweets[], next_cursor }
GET  /v1/users/{id}/tweets?cursor=…
POST /v1/users/{id}/follow    DELETE /v1/users/{id}/follow

Cursor-based pagination is stable for an infinitely growing feed (see APIs).

4. Data model

Data Store Notes
Tweets (tweet_id, author_id, text, media, created_at) Sharded store (by tweet_id or author_id) + tweet cache Snowflake IDs are time-sortable (unique IDs)
Follows (follower_id, followee_id) Graph or wide-column, indexed both ways “Who follows X?” and “Who does X follow?”
Home timeline Redis list per user of tweet IDs, capped at ~800 Precomputed, IDs only
User timeline Query tweets by author_id, time-ordered Also cacheable

5. High-level design

ClientPost serviceTimelineserviceTweet store+ cacheFan-outqueueFan-outworkersFollowergraphTimeline cache(Redis lists)POST tweetGET timelinesavetweet createdfollowers?push IDIDshydrate
Post path fans out into cached timelines; read path fetches IDs and hydrates them

6. Deep dives

Fan-out on write vs on read

Fan-out on write (push)

  • On post, insert the tweet ID into every follower’s timeline cache
  • Reads are trivial: fetch a ready-made list. Very fast
  • Write cost = number of followers: terrible for celebrities
  • Wasted work for inactive followers who never look

Fan-out on read (pull)

  • On read, fetch recent tweets from everyone you follow and merge
  • Writes are trivial: just store the tweet
  • Read cost = number of followees: slow for people following thousands
  • Repeated work on every timeline load

The hybrid answer

Timeline servicePrecomputed IDs(fan-out on write)Celebritiesyou followMerge + sortby timeYour timelineread listrecent tweets
Hybrid: precomputed timeline for normal accounts, merged at read time with celebrities' recent tweets
  • Accounts below a follower threshold (say 100k) use fan-out on write.
  • Celebrities are not fanned out. At read time, the timeline service fetches recent tweets from the (few) celebrities you follow and merges them with your precomputed list.
  • Reads stay fast (one cached list plus a handful of cached celebrity timelines) and no single post triggers 100M writes.

Timeline storage and hydration

  • Store only tweet IDs in each timeline list (LPUSH, then LTRIM to 800). Small and cheap.
  • At read time, fetch IDs from the cursor position, then hydrate them in one batch from the tweet cache (a multi-get), with author info from a user cache.
  • Deleted tweets: hydration skips missing IDs, so no need to touch millions of lists.
  • Cache miss (a user returning after months): rebuild by pulling from their followees, which is the fan-out-on-read path.

Ranking (extension)

For a ranked feed, generate candidates as above, then score them by engagement, relationship strength and recency with a lightweight model, and cache the ranked page. Mention it, but keep the core design chronological unless asked.

Scaling the parts

  • Tweet store: sharded by tweet_id; the user timeline needs an index by (author_id, created_at). Hot tweets live in the tweet cache.
  • Follower graph: sharded by user ID; celebrity follower lists are huge, so stream them in pages to the fan-out workers.
  • Fan-out workers: horizontally scaled consumers of a partitioned queue; idempotent pushes (re-pushing an ID already present is harmless if we dedupe on read).
  • Media: uploads go to object storage via pre-signed URLs and are served through a CDN, which is most of the bandwidth.

7. Bottlenecks and failure modes

What fails Impact Mitigation
Fan-out backlog Tweets appear late Autoscale workers; prioritise active followers; it’s eventual anyway
Timeline cache shard lost Some users see empty feeds Replicas; rebuild on miss via fan-out on read
Viral tweet Hot key in the tweet cache Replicate hot keys; small in-process cache
Celebrity posts burst Read-side merge hot spot Cache celebrities’ recent-tweets lists aggressively
Graph store slow Fan-out stalls Queue absorbs it; backpressure and retries

8. Wrap-up

A strong answer: do the read and write estimates; compare push vs pull clearly; solve the celebrity problem with a hybrid; store IDs and hydrate; use cursor pagination; explain why eventual consistency is acceptable.

Likely follow-ups: How would you implement a ranked “For you” feed? How do you handle unfollows (filter at read time, cleaned up lazily)? How would you add search or trending topics (stream processing over the tweet firehose)?

Test yourself

Answer in your head, then click a card to check. All cards are in the Anki deck.