LearnastraTHE DESIGN COURSE

Design complete systems / CHAPTER 53

Design a microblogging service (Twitter)

Publish short posts and build follower feeds, using prepared lists for ordinary authors and read-time merging for large audiences.

Advanced3h 20m of deliberate study10 questions

Builds on Design a photo-sharing service (Instagram), Design a messaging service (Facebook Messenger), Read-heavy versus write-heavy workloads, Push versus pull architecture, Database indexes.

By the end, you can
  • Keep original posts separate from copied feed IDs and check the current records before display.
  • Compare preparing follower lists with gathering authors’ posts when a viewer reads.
  • Explain how later pages retain their order, how unfollowing removes posts, and how deletion blocks old feed/search copies.
INTERVIEW STEP 1 / 7Clarify the requirements

01Functional requirements

An author timeline lists one author’s posts. A home feed combines posts from authors the viewer follows. Prepared home lists store post IDs; the original post record still decides its text, deleted state and audience.

  1. FR1: Publish and remove posts. Create short posts and replies without duplicating them on request retries. Let authors delete owned content. Media references come from verified photo/video uploads.

  2. FR2: Follow authors. Support following, unfollowing and protected-author access rules. Reposts and search results must still check whether the viewer may see the original post now.

  3. FR3: Read timelines. Serve author timelines and a home feed of followed authors' posts. Start with newest-first ordering. For ranked feeds, later pages continue the same saved post order. If ranking fails, order the posts by creation time instead.

  4. FR4: Record engagement. Let each account set or remove one like per post. Store a repost as a reference to the original. Show permitted replies without exposing an unavailable parent.

  5. FR5: Search post text. Find matching post IDs through a text index, then retrieve their current records and check permission before showing results.

Out of scope

  • Direct messaging and advertising.
  • Suggestions outside the follow graph. Trending topics, mentions and curated products need their own requirements.

Example: Mira publishes p81. Ben sees it in his feed, replies with p82 and likes or reposts p81. His repost does not create an independent copy that can ignore Mira's later deletion.

02Nonfunctional requirements

Assume 100 million daily active users. Five percent publish twice daily, and each user opens ten home-feed pages per day. Confirm and test these planning assumptions.

  1. NFR1: Feed latency. At least 95% of primary-region home-feed requests finish within 300 ms at the planned peak.

  2. NFR2: Feed availability. Home feeds meet 99.9% availability per calendar month. If optional ranking fails, use a limited newest-first fallback.

  3. NFR3: Publication durability. A successful publish means the post and recoverable work for its later distribution have been saved. Accepted posts survive one storage-node failure under the selected replication policy.

  4. NFR4: Home freshness. At normal load, at least 95% of ordinary-author posts enter eligible active followers' prepared feeds within five seconds. Very large authors use read-time merging instead; neither approach promises instant visibility everywhere.

  5. NFR5: Deletion and audience. Stop returning deleted content in newly generated responses within 60 seconds, including through caches. Check current protected-post permission on indirect reads too. Old screens and already downloaded bytes cannot be recalled.

  6. NFR6: Safe retries and delayed updates. Repeated events create only one recipient/post feed entry. Repeated likes describe one account action. An older update cannot undo a newer deletion. Displayed totals may lag, but must be repairable from the saved actions.

  7. NFR7: Ordering and search delay. Order equal creation times or scores by post ID. A cursor names the saved ordering and last returned position, so the next page continues that browsing session rather than starting a newly ranked list. No global order across all authors is required. Target 30-second search propagation, while current visibility always overrides an old search result.

INTERVIEW STEP 2 / 7Estimate the scale

03Quantify read and fan-out amplification

  1. Publishing: five million authors × two posts/day = ten million posts/day, about 116 creations/second.
  2. Feed reads: 100 million users × ten pages/day = one billion requests/day, about 11,600/second. At 20 posts/page, separately fetching every record could mean 232,000 post lookups/second before batching or caches.
  3. Prepared home lists: if each ordinary post adds a reference for 200 active followers, ten million posts create two billion entries/day. At 32 bytes each, IDs alone need 64 GB/day before indexes, replicas and retention.
  4. Exceptional authors: one author with ten million followers can create ten million list updates for one post. The average publishing rate hides this burst.

Design decisions

  • Bound page size, fetch posts in batches and cache frequently reused records.
  • Prepare ordinary-author home lists in background programs, using small references rather than copying full text.
  • Compare the cost of a large author's many list writes with merging their recent posts when a follower reads.
INTERVIEW STEP 3 / 7Define the API

04API Design: Requests, Responses and Retries

Identify one publishing request with a request key. If the client retries after a timeout, return the original post instead of creating another:

POST /posts
Idempotency-Key: publish-81
{"text":"Garden opens Saturday.","replyTo":null,"mediaIds":[],"visibility":"public"}
{"postId":"p81","version":1,"state":"published","createdAt":"2026-10-24T12:00:00Z"}

Creation results

  • 201 Created: the post, original request result and pending feed/search work have been saved together.
  • Same request key and content: return the original p81 result.
  • 409 Conflict: that key was used with different content. 400: text length is invalid. 403: the account may not publish.

Replies, likes and reposts

  1. A reply calls POST /posts with replyTo p81. First check that its author may read the parent post.
  2. PUT /posts/p81/likes/me saves the signed-in account's like; DELETE removes it. Repeating the same operation keeps the same final state.
  3. A repost saves the original post ID p81 rather than a separate copy of its text. Later reads fetch the current original and check its current visibility.

Browse posts

  • GET /authors/M/posts?cursor=... lists Mira’s posts; M is her account ID.
  • GET /home?cursor=... returns the viewer's home feed.
  • GET /search?q=garden&cursor=... searches posts.
  • Each page contains post IDs, current display details and a continuation cursor. Finding a post in a feed or search result never bypasses its permission check.
  • Ben calls PUT /authors/M/follow to follow Mira and DELETE /authors/M/follow to unfollow. The signed-in account determines the follower; repeating the operation leaves the same final relationship state. A protected author must approve the relationship before its posts become readable.
  • An author approves or revokes a follower with PUT or DELETE /followers/{accountId}/approval; the handler checks account ownership. DELETE /posts/p81 checks Mira owns the post and saves a newer deletion version. A repost request names originalPostId p81 and saves a reference after checking that the caller may read it.
INTERVIEW STEP 4 / 7Define the data model

05Data Model and Storage Access Patterns

Record and lookup key What it stores and why
Post, postId; author/time index Text, author, parent reply ID, media references, visibility, version and deletion time; fetch one post or one author's recent posts
Follow, (followerId,authorId); reverse author index Current follows; find who follows a publishing author
Like, unique (postId,accountId) One account's current like; repeated requests cannot create additional likes
Repost, (accountId,originalPostId) Original post ID and repost time; fetch current text and visibility when shown
Home entry, (recipientId,sortKey,postId); unique (recipientId,postId) A post ID and its ordering information for one viewer; rebuild this list from posts and follows if needed
Publication/change record, eventId Saved work describing a particular post version; workers update feeds, search and counts from it
  1. A displayed count can lag the saved likes. The individual like rows remain the record of who liked the post and allow totals to be rebuilt.
  2. To load a reply thread efficiently, add an index beginning with parent post ID and reply time. Without it, the database may have to inspect unrelated posts.
INTERVIEW STEP 5 / 7Draw the high-level design

06Start with fan-out on read

Start with one database containing posts and follow relationships. A post's ID, such as p81, identifies its saved text and current visibility.

Publish once, including after a timeout

  1. Mira's client labels her request publish-81. The application checks whether that account/request key already has a saved result.
  2. If the content matches a previous attempt, return its p81 result. Reject changed content under that key.
  3. For a new request, one transaction saves the post, the request result and pending work for later feed/search updates. A unique request key prevents simultaneous retries from creating duplicates.

Build Ben's first home feed

  1. Find the authors Ben currently follows.
  2. Fetch a limited recent list from each author using the author/time index.
  3. Merge those posts newest first, check current visibility and return the first page.
  4. This is fan-out-on-read, or pull: gather the posts when Ben reads. It needs no prepared follower home list.

If Ben follows thousands of authors, each page can require many queries and comparisons. Cap source work and measure it. More parallel queries may reduce elapsed waiting while still consuming substantial storage/network capacity. The next stage prepares common results before readers ask.

BUILD THE DESIGN, ONE CHANGE AT A TIME

Watch the architecture evolve.

Advance through the stages in this same diagram. Existing components keep their places; additions fill the available space, then add rows. Highlights show what changed from the previous stage, and numbered arrows match the connection key below.

Stage 1 of 3

RetainedAdded / new routeChanged responsibility / route

Start here. A bounded follow graph can use simple indexed source queries before a distributed home index is needed.

Stage 1: chronological fan-out on read

Stage 1: chronological fan-out on readCommit p81 once. Find Ben's followed authors. Merge a bounded page from source author indexes. Numbered arrows correspond to the connection key below. All connections are directional.Publishing authorCreate once; recoverthe saved resultPost / feedapplicationCreate posts; loadand check feed itemsPost + followdatabasePosts, author listsand permissionReading userBounded home-pagerequest1234Stage 1: chronological fan-out on readCommit p81 once. Find Ben's followed authors. Merge a bounded page from source author indexes. Numbered arrows correspond to the connection key below. All connections are directional.Publishing authorCreate once; recoverthe saved resultPost / feedapplicationCreate posts; loadand check feed itemsPost + followdatabasePosts, author listsand permissionReading userBounded home-pagerequest1234Stage 1: chronological fan-out on readCommit p81 once. Find Ben's followed authors. Merge a bounded page from source author indexes. Numbered arrows correspond to the connection key below. All connections are directional.Publishing authorCreate once; recoverthe saved resultPost / feedapplicationCreate posts; loadand check feed itemsPost + followdatabasePosts, author listsand permissionReading userBounded home-pagerequest1234

Follow the numbered arrows

  1. Publishing author → Post / feed applicationPublish / retry request publish-81
  2. Post / feed application → Post + follow databaseCommit post / query followed authors
  3. Reading user → Post / feed applicationRead chronological home
  4. Post / feed application → Reading userCurrent permitted post details
The same source records support publication, author pages and baseline home discovery without copying post bodies into recipient lists.
Trace the diagram in words
  1. Commit p81 once.
  2. Find Ben's followed authors.
  3. Merge a bounded page from source author indexes.
What this stage establishes
  • Store one source post and follow relationships.
  • Merge recent author lists and apply current visibility for each page.

The cost: Publication is cheap, while every home request pays followed-author query and merge work.

Read the reasoning for this stage →
Smallest working system
INTERVIEW STEP 6 / 7Go deep

07Generate and place IDs without losing timeline access

Choose a post ID

Method How it gets uniqueness Cost
Database sequence One issuer assigns distinct integers Coordination with the issuer
Reserved ranges Each server gets a saved, non-overlapping range Track assignments; skip unused numbers after crashes
Random IDs Choose a large unpredictable value and handle duplicates No inherent chronological order
Time/worker/sequence Distinguish time, server and local counter Correct worker assignment and clock/restart rules

Understand a toy time-based ID before choosing it

  1. At time tick 1042, server 07 emits 1042-07-00; server 09 emits 1042-09-00. Different server numbers keep these IDs distinct.
  2. Server 07's next ID is 1042-07-01. No two live generators may use the same server number.
  3. If server 07 uses every sequence number for tick 1042, wait for a later tick before resetting the counter. If its clock moves back to 1041, pause rather than issue combinations it may already have used.
  4. Before issuing IDs for a tick, durably save that tick or a higher reserved limit. After a restart, recover that saved limit and wait until time advances beyond it before reusing worker 07. If that record cannot be recovered or the previous process may still run, obtain a different unused worker number or pause generation. Never assume a restart makes earlier IDs available again.
  5. This demonstrates the required checks; choose a tested generator that supplies them and document its time/worker/counter allocation. A timestamp alone does not guarantee uniqueness.

Choose record placement separately

  • Author-based partitions keep one timeline together but can overload a popular author's partition.
  • Hashing postId spreads rows, so keep an author/time index to find an author's recent IDs.
  • Creation-time ranges concentrate new traffic unless further divided. Map logical partitions to machines so selected groups can move deliberately.
  • A home list contains IDs. Fetching the current text, audience and media behind those IDs is called hydration. Batch these lookups by partition; a 20-post page should not require 20 independent network round trips.
  • Add memory first if adequate, then replicas for public reads that tolerate delay. An author's immediate publication confirmation needs a read known to contain that write.

Worker IDs distinguish otherwise identical time and counter values

Worker IDs distinguish otherwise identical time and counter valuesTwo workers can have the same local sequence and time. Worker IDs 07 and 09 keep the two IDs different even when their tick and local counter match. Save the tick limit before issuing IDs, wait beyond it after clock recovery, and never assign one worker ID to two live generators. Numbered arrows correspond to the connection key below. All connections are directional.Worker 07 at tick1042Local sequence 00Worker 09 at tick1042Local sequence 001042-07-00Distinct postidentity1042-09-00Distinct postidentityAuthor / timeindexOrder separate fromplacement1234Worker IDs distinguish otherwise identical time and counter valuesTwo workers can have the same local sequence and time. Worker IDs 07 and 09 keep the two IDs different even when their tick and local counter match. Save the tick limit before issuing IDs, wait beyond it after clock recovery, and never assign one worker ID to two live generators. Numbered arrows correspond to the connection key below. All connections are directional.Worker 07 at tick1042Local sequence 00Worker 09 at tick1042Local sequence 001042-07-00Distinct postidentity1042-09-00Distinct postidentityAuthor / timeindexOrder separate fromplacement1234Worker IDs distinguish otherwise identical time and counter valuesTwo workers can have the same local sequence and time. Worker IDs 07 and 09 keep the two IDs different even when their tick and local counter match. Save the tick limit before issuing IDs, wait beyond it after clock recovery, and never assign one worker ID to two live generators. Numbered arrows correspond to the connection key below. All connections are directional.Worker 07 at tick1042Local sequence 00Worker 09 at tick1042Local sequence 001042-07-00Distinct postidentity1042-09-00Distinct postidentityAuthor / timeindexOrder separate fromplacement1234

Follow the numbered arrows

  1. Worker 07 at tick 1042 → 1042-07-00Include the assigned worker ID in the post ID
  2. Worker 09 at tick 1042 → 1042-09-00Same tick, different worker
  3. 1042-07-00 → Author / time indexStore author access entry
  4. 1042-09-00 → Author / time indexFind the partition and load current post details
The example shows why time plus an uncoordinated local counter is insufficient; identity and timeline lookup remain separate design concerns.
Trace the diagram in words
  1. Two workers can have the same local sequence and time.
  2. Worker IDs 07 and 09 keep the two IDs different even when their tick and local counter match.
  3. Save the tick limit before issuing IDs, wait beyond it after clock recovery, and never assign one worker ID to two live generators.

08Introduce a hybrid home-feed pipeline

Prepare ordinary-author feeds

  1. Save a pending distribution record with Mira's post. This record is an outbox entry: the database remembers work that still needs to be sent after a commit.
  2. A relay program sends that work to a queue and retries if a response is lost. A worker reads follower pages and records where it reached.
  3. For each active follower, insert (viewerId,postId) only if it is absent. The database's unique pair prevents repeated work from adding another feed entry.
  4. Ben now reads one short prepared list and fetches current post details in batches. Check both that he still follows each author and that he may currently view the post. Old list entries do not preserve a follow relationship. Store a limited recent window grouped by recipient.
  5. This is fan-out-on-write, or push: publication schedules many list writes so later reads do less work.

Handle very large authors on reads

  1. Copying one post to millions of followers can dominate background capacity. In the final stage, skip prepared-list insertion for designated large authors.
  2. Keep their recent author IDs, optionally cached. On a cache miss, reload the author/time index.
  3. Merge a limited recent set into Ben's prepared list when he reads, then load and check current records before showing text.
  4. Estimate required feed insertions as posts/second × active followers, then compare it with tested worker capacity and the freshness target. For example, ten million inserts at an assumed 25,000 inserts/second take at least 400 seconds before overhead. Merging that author’s posts when followers read avoids that publication burst, but adds work to those reads.

Recovery costs

  • Saved outbox work survives a crash between publication and queue delivery.
  • Worker checkpoints let follower-page processing resume.
  • Unique recipient/post pairs make repeated insertions harmless.
  • Candidate caches find possible posts; they never grant current permission.
See stage 2: Prepared home candidates ↗See stage 3: Hybrid author delivery ↗

10Trace posts, reads and follow changes

  1. Publish: Mira submits publish-81. One transaction saves post p81, its request result and pending event e81 for follow-up work. Here e81 labels that work, not a second post.
  2. Prepare: a worker inserts (Ben,p81) only if absent. Replaying e81 cannot duplicate Ben's list entry.
  3. Search: another worker adds p81's words to the text index. Search visibility has its own processing delay.
  4. Read: Ben’s feed handler combines prepared IDs with large-author IDs, fetches current records and checks current follows and post visibility before returning a stable page. The saved list controls the order of later pages; it grants no continuing access after Ben unfollows an author.
  5. Like: repeated PUT /posts/p81/likes/me keeps one Ben/p81 action. A background program counts each identified change once and can repair totals from saved actions.
  6. Unfollow: Ben removes his follow relationship to Mira. A prepared entry for p81 may remain, and a delayed worker may insert it again. The next home read checks the current relationship and omits it; background cleanup later removes the stale entry. Directly opening a public author page follows that page’s access rules instead.
  7. Delete: Mira saves deletion version 2. Old creation version 1 cannot undo it. A repost still refers to p81 and must check that original’s current state.

Continue pages predictably

  • For a newest-first page, order by creation time and then post ID. Capture a maximum creation time on the first page, and include that limit plus the last returned time/ID in the cursor. Later pages exclude posts with creation times after that limit and continue after the last returned pair. A later database insert can carry an older timestamp, so this does not freeze which posts exist. If fixed membership is required, save the ordered IDs as in the ranked-page approach below.
  • For ranked pages, save the ordered candidate post IDs under a view ID with an expiry. The cursor contains that view ID and the last returned position. Later pages use the saved order while still rechecking follows and visibility. If the view expired, ask the client to restart.
  • Remembering already-shown IDs prevents some repeats, but cannot recover posts skipped because the service recalculated a different order between requests.
INTERVIEW STEP 7 / 7Find bottlenecks and failure points

11Plan failure, moderation and cost

  1. Distribution is late: author pages can read saved posts while home lists lag. Monitor the oldest unfinished follower batch; a short queue can still contain very old work.
  2. Fallback overload: cap read-time author queries. Replacing every failed prepared-feed request with thousands of database reads can spread the outage.
  3. Deletion has not reached every copy: save deleted state in the original, remove cached bodies within the promise and clean discovery lists in the background. Check current state before display, including through reposts.
  4. Retries: retain original request results and unique list pairs. Queue redelivery is expected after failures; it must not create another post or list entry.
  5. Abuse and monitoring: limit rapid posting/follow changes, isolate abusive accounts and avoid sensitive log tokens. Measure publish/feed latency, duplicate attempts, distribution age and cache reuse.
  6. Defend the cost: prepared lists spend storage and background writes to save repeated read work. Large-author merging limits that cost while preserving current permission checks.

OPTIONAL DESIGN EXTENSIONWhere AI Fits in This Design

Use: rank posts the viewer is already allowed to see

  1. Input: a limited set of posts the viewer may see, their creation times, and prior likes or replies the viewer has agreed may be used for recommendations.
  2. Output: scores for those same posts. The model cannot add a forbidden/deleted post or treat a repost as permission.
  3. Integration: gather candidate IDs, check current visibility and rank within an assumed 50 ms slice of the 300 ms page target. Recheck visibility before display.
  4. Pagination: save the model version and ordered candidate IDs under a view ID with an expiry. Later-page cursors use that ID and the last returned position, rather than rerunning the model and shuffling the order.
  5. Fallback: if scoring exceeds 50 ms or the interaction history cannot be loaded, order the permitted candidates by creation time, newest first. Model failure must not stop publication, deletion or likes.
  6. Evaluation and limits: test recommendations against interactions not used for training, then try the model with a small group before expanding. Check whether it repeatedly shows one author, promotes clickbait or uses outdated interaction data. Measure user feedback, cost per page and slow requests. Set a deletion period for stored private interaction history.

TensorFlow Ranking documents model-based list scoring. The product still chooses the objective; more clicks alone do not define a useful feed.

DRAW. CALCULATE. EXPLAIN.

Choose fan-out policy with an explicit write budget

75 minutes · guided practice

Mira publishes p81 with ID components time tick 1042, worker 07 and local sequence 00. Worker 09 also uses sequence 00 at that tick. Ben retries the same like three times and reposts p81. A delayed publication record arrives after deletion version 2.

  1. Explain why the generated IDs differ and why Ben’s retries create only one like.
  2. Trace the saved post, feed/search copies and deletion; show how old work is rejected.
  3. For an author with ten million followers and a hypothetical budget of 25,000 follower-feed inserts/s, calculate the ideal completion time and choose push, pull or hybrid delivery.
  4. Compare raw storage for two billion 32-byte post references with two billion copied 1 KB bodies.
Your self-check
Compare the worked solution
  1. The toy IDs 1042-07-00 and 1042-09-00 differ by their uniquely assigned worker IDs. Keep worker ownership, counter limits and clock/restart rules explicit; a database sequence is a simpler starting alternative.
  2. Retrying publish-81 returns p81. Repeated like PUT requests keep one Ben/p81 row, and identified changes increase derived totals only once.
  3. Save distribution work with the original post. Workers remember their follower-page progress and insert each viewer/post pair only if absent.
  4. Deletion version 2 overrides delayed creation version 1. Reposts and search still check the original’s current state before showing text.
  5. Two billion × 32 bytes = 64 GB/day; copying 1 KB each takes about 2 TB/day. References still require current-record lookups and permission checks.
  6. Ten million insertions ÷ 25,000/second = at least 400 seconds, or 6 minutes 40 seconds, before overhead. Prepare ordinary-author lists, but merge this exceptional author’s recent posts during reads instead of promising five-second mass distribution.

The checkboxes record your own assessment. The worked solution gives you a way to check the reasoning.

RETRIEVE BEFORE YOU REVEAL

Keep the important ideas.

Say the answer aloud, then turn the card over.

CARD 01How does a post reach several product views?
  1. Save the original post, retry result and pending feed/search work.
  2. Workers copy its ID into discovery lists.
  3. Home reads check current follows, post state and audience.
  4. A repost cannot bypass original deletion.
CARD 02What does an ID generator need beyond a timestamp?

A timestamp alone is insufficient.

  • Give simultaneous generators distinct worker IDs or number ranges.
  • Wait for the next tick when that tick’s counter is full.
  • Pause after a backward clock move.
  • Before issuing IDs, save the tick upper bound. After a restart, recover it and wait beyond it, or obtain a different unused worker ID.
  • Keep a separate author/time index for listing posts.
CARD 03Why fan out IDs and keep source actions?
  • Prepared feeds store IDs to avoid copying every body.
  • Reads fetch current text and permission.
  • One saved account/post like makes retries harmless and totals repairable.
  • Merge very large authors on reads to limit list writes.
Practise this topic in the revision deck ↗

CHECK THE REASONING

Can you explain the choice?

Answer every question, then finish for an immediate score and an explanation for each answer. Aim for 80%, then revisit any gaps.

1 / 10Ben retries PUT /posts/p81/likes/me three times. What source state should result?
2 / 10Workers 07 and 09 both issue local sequence 00 at tick 1042. What distinguishes the toy IDs?
3 / 10p81 is deleted, but a repost points to it. What should a current response do?
4 / 10Hashing postId across machines improves balance. What access path is still needed?
5 / 10Why can a published post be absent from basic search briefly?
6 / 10A delayed create arrives after a newer deletion. Which update rule avoids resurrection?
7 / 10At two billion recipient entries/day, why prefer 32-byte references to 1 KB copied bodies?
8 / 10A worker clock moves backward in a time/worker/sequence generator. What is necessary?
9 / 10Which workload most directly favors pull-only handling of one author?
10 / 10The learned ranker misses its 50 ms budget while an eligible candidate page exists. What should the feed do?
0 of 10 answered

CLOSE THE LOOP

Explain it without the page.

Summarise the mechanism, one trade-off, and one failure mode. Then mark this chapter complete.

Go to the source