LearnastraTHE DESIGN COURSE

Design complete systems / CHAPTER 59

Design a ranked news feed (Facebook)

Build follower feeds from saved posts, limit the work needed for each page and recheck who may see each result.

Advanced3h 20m of deliberate study10 questions

Builds on Design a microblogging service (Twitter), Push versus pull architecture, Normalization versus denormalization.

By the end, you can
  • Explain how a feed finds possible posts, orders them and loads their display content.
  • Choose when to prepare follower lists and when to read an author’s posts on demand.
  • Preserve current audience checks and one page order even when background follower-list updates are delayed.
INTERVIEW STEP 1 / 7Clarify the requirements

01Functional requirements

A news feed combines posts from people and pages the viewer follows. Some posts will be ready in a prepared list; others can be collected when the viewer opens the feed. Neither route may bypass the post’s current audience rules.

  1. FR1: Post publication. Publish text and media references with a stable request identity. A successful response means the source post and a recoverable record of required feed-update work have both committed.

  2. FR2: Follow relationships. Let an authenticated viewer follow or unfollow an author. In this design, an unfollow excludes that author’s historical posts from subsequent feed requests.

  3. FR3: Ranked feed pages. Return a size-limited page of currently permitted posts from followed people and pages, ordered by the chosen ranking. Return a continuation cursor that identifies the same result traversal when requesting the next page.

  4. FR4: Post deletion. Let the author delete a post. Stale candidate references must not cause a later response to return its deleted body.

  5. FR5: Audience updates. Let the author change a post’s audience. Relevance scores and old feed membership do not grant access under the new audience rule.

  6. FR6: Comments. Store comments separately so permitted readers can display them with a post. Ranking may use a comment-count feature without loading every comment body while building the feed.

  7. FR7: Reactions. Store each user’s reaction separately from the post body so it can be displayed or used as a ranking feature without copying mutable post content into every feed.

Out of scope

  • Advertising auctions and a complete machine-learning training platform.
  • Erasing content already downloaded or displayed on someone else’s device. Server-side deletion and unfollow rules govern subsequent responses.

Example. Mina follows ordinary author Arun and a page with ten million followers. Arun publishes P8. We will trace P8 from its source transaction to Mina’s feed, then compare ordinary-author fanout with the large page’s read-time merge.

02Nonfunctional requirements

These are independently chosen exercise assumptions to validate, not measured production guarantees.

  1. NFR1: Page latency. At least 95% of measured feed requests must finish within 300 ms, from receipt to completed response. Include finding possible posts, checking permission, ranking and loading display records. Fetching referenced media on the device is a separate measurement.

  2. NFR2: Candidate freshness. A new permitted post must become available for the ranking stage within five seconds of its source commit. Such a possible result is called a candidate. This does not guarantee its position in the feed or delivery to every connected device.

  3. NFR3: Peak page capacity. Plan for about 11,570 pages/second at the assumed peak derived below. Test the viewer-follow distribution and large-author bursts rather than relying on an average author.

  4. NFR4: Bounded ranking work. Score at most 500 possible post IDs per page. Limit extra fetching when permission checks remove items. If richer scoring misses its deadline, return permitted candidates newest first, breaking equal creation times by post ID.

  5. NFR5: Eligibility correctness. Return zero deleted or currently forbidden post bodies. The handler checks the main post record and current audience before returning a body, even if an old feed entry, cache or model score names it. Omit the item when permission cannot be established.

  6. NFR6: Publication recovery. Once publication succeeds, a worker or message-relay crash must not lose the saved post or unfinished follower-list work. The publication handler saves both together before reporting success, using the database’s agreed durable commit settings. Later list updates and notifications may be delayed without erasing the post.

INTERVIEW STEP 2 / 7Estimate the scale

03Back-of-the-envelope Estimates

Count both the page requests and the work performed to prepare each page. These decimal-byte estimates exclude media delivery unless stated.

  1. Feed reads. Five million daily users open twenty pages each: 5 million × 20 = 100 million pages/day. That is about 1,157 pages/second on average and 11,570/second at a chosen tenfold peak. The handler must assemble pages at that rate, not merely accept connections.
  2. Page payload. Twenty displayed posts at 1 KB of references and metadata each make 20 KB/page. 100 million × 20 KB = 2 TB/day before media and transport overhead. Deliver media from object storage through a CDN; do not copy image bytes into each follower’s feed record.
  3. Prepared follower lists. One million posts/day, each sent to 200 followers, create 1 million × 200 = 200 million reference inserts/day. At 32 bytes each, that is 6.4 GB/day raw, or 44.8 GB for seven days before indexes and replicas. The ordinary average is about 2,315 inserts/second.
  4. A large author. One post from the page with ten million followers would request ten million inserts at once. The average above hides this burst. Compare that work with reading the author’s timeline only for people who actually open a page.
  5. Ranking work. At most 500 candidates/page times 11,570 peak pages/second gives about 5.8 million evaluations/second. At an assumed 10 microseconds each, scoring alone consumes about 58 CPU-seconds every second, roughly 58 fully busy cores. Retrieving features and candidates adds more work; benchmark the actual scorer and retain a cheap fallback.
INTERVIEW STEP 3 / 7Define the API

04API Design and Retry Contracts

Arun’s app asks the post handler to publish text. The handler saves a post identified as P8; version 8 is that record’s current change number, used later to distinguish a newer deletion from this creation.

POST /posts
Idempotency-Key: publish-P8
Content-Type: application/json
{"text":"The garden is open","mediaRefs":[],"audience":"followers"}
{"postId":"P8","version":8,"state":"published"}
  1. Publish once. POST /posts takes text, media references, audience and a stable idempotency key. The server derives Arun’s identity from login. Return 201 only after saving the post and its required follow-up work. A matching retry returns the same publication result; saving the post does not mean all follower lists are already updated.
  2. Manage follows. POST /follows takes an author ID; the follower comes from authentication. DELETE /follows/{authorId} removes that relationship. The next feed request excludes the unfollowed author’s historical posts under this chapter’s rule.
  3. Read a page. GET /feed?limit=20&cursor=… returns permitted posts and an opaque continuation cursor. The handler saves a limited ordered list of candidate IDs and scores under a view ID with an expiry. The cursor carries that view ID and the last (score,postId) position. Later pages continue that saved order and still remove posts the viewer may no longer read; a page number alone cannot stabilize changing scores. Return 400 for malformed cursors and an explicit restart response for expired views.
  4. Edit or delete. PATCH /posts/P8 changes the audience only for the author and expected current version. DELETE /posts/P8 records deletion with a newer version. Save each source change and its follow-up event together so workers can repair their copies.
  5. Add engagement. POST /posts/P8/comments uses a stable request key. PUT /posts/P8/reaction derives the reacting user from login. Both check current post access and save separate comment or reaction records; neither overwrites a feed copy of the post body.
INTERVIEW STEP 4 / 7Define the data model

05Data Model and Access Paths

The main post database decides which post exists, its current text and who may read it. A feed list only stores IDs of possible posts. Loading the current display records for those IDs is called hydration.

Record / key Fields and index Reason
Post postId Author, text/media refs, audience, creation time, version, deletion state; (authorId,createdAt,postId) index Source of truth and author timeline
Follow (followerId,authorId) State/version; reverse index on author Read sources and enumerate followers
Feed entry (viewerId,postId) Score hint, creation position, source version Deduplicated derived candidate
Comment (postId,commentId) / reaction (postId,userId) Body/state or reaction type Display and bounded engagement features
Outbox eventId Post ID, event type, version, relay state Retry publication after commit
  • The publication handler also saves the result for (authorId,requestKey). That lookup makes a repeated create request return P8 rather than create another post.
  • The outbox is a table of work still needing delivery, such as “P8, version 8, created.” The handler writes that row in the same transaction as P8. If it crashes before notifying a worker, another program can find the row later.
  • The relay is the program that reads pending outbox rows and sends them to feed workers. It may send twice if the first send succeeded but the relay crashed before recording progress.
  • The feed worker inserts one (viewerId,postId) pair per follower. A database uniqueness rule rejects a second identical pair, so repeating delivery does not put P8 twice in Mina’s list.
  • The source record retains newer versions and deletion state. Workers compare these before applying old work, and the read handler checks current post, audience and follow rules before returning a body.
INTERVIEW STEP 5 / 7Draw the high-level design

06Baseline: Start with pull, then measure

Begin without prepared follower lists. Mina follows Arun and a page with ten million followers. When she opens the feed, collect recent posts from those authors. Doing this when a reader asks is called fanout on read, or pull.

  1. Arun’s publication handler saves P8 and its publication result in the main database. The record contains its author, text, media references, audience and version.
  2. Mina’s browser calls GET /feed. The feed handler verifies Mina and reads the author IDs she currently follows.
  3. For each author, it uses the (authorId,createdAt,postId) index to load a limited recent slice of post IDs. The limit prevents one author’s whole history from being scanned.
  4. It combines those IDs, removes duplicates and reads current post state and audience rules. P8 remains only if Mina may currently read it and still follows the author.
  5. It scores a bounded set, initially using simple recency with a small engagement contribution. It selects the page and loads its display fields; this loading step is hydration.
  6. It returns the page with a cursor tied to the ranking view. Mina’s device requests any referenced media through its own permitted delivery path.

Trace P8 through those steps: it becomes a possible result when the handler reads Arun’s timeline, survives Mina’s audience check, receives a score and supplies its current text. No precomputed list is necessary yet.

The drawback appears when Mina follows 500 active sources: producing twenty results may require hundreds of timeline reads. Group reads where supported and cap the work. Parallel calls can shorten waiting, but still consume network and storage. Pull remains attractive for inactive followers because no feed work happens until they return.

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 viewer follows few enough active authors that the handler can read their recent timelines within the page budget.

Pull feed

Pull feedThe handler reads the authors Mina follows. It reads Arun’s recent IDs and checks whether Mina may read P8. It scores eligible IDs and loads the selected text and media references. Numbered arrows correspond to the connection key below. All connections are directional.Feed readerMina requests a pageand cursorFeed servicePull followedauthors; simple scorePost + timelinestoreAuthoritativeversioned postrecordsFollow authorityCurrent relationshipsand reverse lookupEligibility +hydrationCurrent access; loaddisplay recordsMedia deliveryObject storage/CDNunder its policy1234567Pull feedThe handler reads the authors Mina follows. It reads Arun’s recent IDs and checks whether Mina may read P8. It scores eligible IDs and loads the selected text and media references. Numbered arrows correspond to the connection key below. All connections are directional.Feed readerMina requests a pageand cursorFeed servicePull followedauthors; simple scorePost + timelinestoreAuthoritativeversioned postrecordsFollow authorityCurrent relationshipsand reverse lookupEligibility +hydrationCurrent access; loaddisplay recordsMedia deliveryObject storage/CDNunder its policy1234567Pull feedThe handler reads the authors Mina follows. It reads Arun’s recent IDs and checks whether Mina may read P8. It scores eligible IDs and loads the selected text and media references. Numbered arrows correspond to the connection key below. All connections are directional.Feed readerMina requests a pageand cursorFeed servicePull followedauthors; simple scorePost + timelinestoreAuthoritativeversioned postrecordsFollow authorityCurrent relationshipsand reverse lookupEligibility +hydrationCurrent access; loaddisplay recordsMedia deliveryObject storage/CDNunder its policy1234567

Follow the numbered arrows

  1. Feed reader → Feed serviceGET /feed with traversal context
  2. Feed service → Follow authoritycurrent followed sources
  3. Feed service → Post + timeline storepull bounded recent post IDs
  4. Feed service → Eligibility + hydrationcandidate IDs to validate
  5. Eligibility + hydration → Post + timeline storecurrent body, version, audience
  6. Eligibility + hydration → Feed readerpermitted display records + media refs
  7. Feed reader → Media deliveryload permitted media representation
Mina’s feed handler collects recent post IDs from followed authors, checks current access and loads their display records. No prepared follower list is required yet.
Trace the diagram in words
  1. The handler reads the authors Mina follows.
  2. It reads Arun’s recent IDs and checks whether Mina may read P8.
  3. It scores eligible IDs and loads the selected text and media references.
What this stage establishes
  • The main database saves posts and follow relationships.
  • The feed handler reads a bounded timeline slice per followed author, checks permission and selects display records.
  • Media bytes follow their own permitted delivery path rather than being copied into feed lists.

The cost: Following hundreds of active authors multiplies timeline reads. Parallel requests may shorten waiting but still use storage and network capacity.

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

07Evolution: Fan out ordinary authors; pull exceptional ones

First test whether more memory for frequently read timelines or faster scoring processors extends the simple design. Add prepared lists only when repeated timeline reads become the measured problem.

Prepare ordinary authors’ posts when they publish

  1. Arun’s handler commits P8 and its outbox work row together.
  2. The relay reads that row and sends its event ID, post ID and version to a background feed worker. An event is a record of the already committed change, not another instruction to create P8.
  3. The worker reads Arun’s followers and inserts (Mina,P8), (Leo,P8) and so on into size-limited candidate lists. This copying of references to many recipients is fanout on write, or push.
  4. On repeated delivery, an insert for (Mina,P8) finds the same unique pair and creates no second entry. The worker records progress so it can resume the follower scan after a crash.
  5. Mina’s next feed request reads her prepared list, then performs the same current permission checks, scoring and hydration. The list makes discovery cheaper; it does not authorize content.

Keep very large authors on a read-time path

  1. Retain the large page’s recent timeline instead of requesting ten million follower inserts for every post.
  2. When Mina reads, combine her prepared ordinary-author IDs with a bounded recent slice from that page’s timeline. Remove repeated IDs before scoring.
  3. Check current source versions, deletion, audience and follow relationships. Fetch at most the defined candidate budget; if filtering leaves a short page, do not search forever for replacements.
  4. Score at most 500 candidates, load the selected bodies and recheck any permission that could have changed before return. Use the agreed eligible chronological order if richer scoring misses its deadline.
Policy Who does the extra work? Cost to accept
Pull Feed handler reads author timelines per page Repeated reads for active viewers
Push Worker prepares references per follower Many inserts, including inactive followers
Hybrid Worker pushes ordinary posts; handler pulls exceptional authors Two candidate sources and more merge rules

Choose the threshold using reader activity, posting rate, worker capacity and the five-second freshness target, not follower count alone. Partition follower lists by viewer and timelines by author. Cache immutable media or post versions, but keep current permission checks outside an old response cache. Notifications only tell Mina new candidates exist; a reconnect fetches saved state, and a repair program can rebuild lists from source posts and follows.

One ordinary post becomes references in follower lists

One ordinary post becomes references in follower listsThe handler commits the post and pending publication work. The relay sends that saved event; the worker inserts a unique reference for each follower. Mina’s read loads the references, checks current access and then fetches display content. Numbered arrows correspond to the connection key below. All connections are directional.Arun publishes P8Post + outbox commitFanout workerEnumerate followersMina candidatelistInsert (Mina, P8)Leo candidatelistInsert (Leo, P8)Mina readsCheck access; scoreIDs; load bodies1234One ordinary post becomes references in follower listsThe handler commits the post and pending publication work. The relay sends that saved event; the worker inserts a unique reference for each follower. Mina’s read loads the references, checks current access and then fetches display content. Numbered arrows correspond to the connection key below. All connections are directional.Arun publishes P8Post + outbox commitFanout workerEnumerate followersMina candidatelistInsert (Mina, P8)Leo candidatelistInsert (Leo, P8)Mina readsCheck access; scoreIDs; load bodies1234One ordinary post becomes references in follower listsThe handler commits the post and pending publication work. The relay sends that saved event; the worker inserts a unique reference for each follower. Mina’s read loads the references, checks current access and then fetches display content. Numbered arrows correspond to the connection key below. All connections are directional.Arun publishes P8Post + outbox commitFanout workerEnumerate followersMina candidatelistInsert (Mina, P8)Leo candidatelistInsert (Leo, P8)Mina readsCheck access; scoreIDs; load bodies1234

Follow the numbered arrows

  1. Arun publishes P8 → Fanout workerretry durable event
  2. Fanout worker → Mina candidate listID reference only
  3. Fanout worker → Leo candidate listsame post; other viewer
  4. Mina candidate list → Mina readsretrieve candidates
The post handler saves P8 and unfinished update work together. A worker inserts one unique viewer/post pair per follower; repeating delivery does not create another Mina/P8 entry.
Trace the diagram in words
  1. The handler commits the post and pending publication work.
  2. The relay sends that saved event; the worker inserts a unique reference for each follower.
  3. Mina’s read loads the references, checks current access and then fetches display content.
See stage 2: Pushed candidate lists ↗See stage 3: Hybrid bounded ranking ↗
INTERVIEW STEP 7 / 7Find bottlenecks and failure points

08Failure and Recovery Scenarios

Test the same P8 path with failures that change what a handler or worker may do:

  1. The create event is delivered twice. The worker attempts the same unique (viewerId,P8) insert. The database keeps one reference per viewer. A unique event ID alone would not help if the worker created a fresh feed-entry row on every delivery.
  2. Deletion arrives before an old create. The main record says P8 was deleted at version 9. A delayed worker holding creation version 8 compares versions and refuses to restore it. Retaining the deletion record, called a tombstone, preserves that evidence.
  3. Mina loses access. Arun changes P8’s audience or Mina unfollows him while an old candidate entry remains. The read handler checks current post and relationship records and omits P8. If required permission cannot be checked, it omits the item rather than trusting an old list.
  4. Too many candidates are removed. Bound extra retrieval. Returning fewer posts preserves privacy and the work limit; an unchecked filler post does not.
  5. Ranking changes during paging. The cursor continues one retained ranking view with a stable post-ID tie-break. If that view expires, tell the client to restart. A seen-ID set can suppress repeats but cannot recover all items skipped by arbitrary reranking.
  6. Scoring or fanout is slow. Use a cheap chronological order over permitted candidates when scoring times out. When follower-list work is delayed, show older eligible content with a freshness indication and alert on queue age. The committed source post must remain independently recoverable.

09Trade-offs and Validation

Explain the read and write costs using Mina rather than only naming a feed service.

  • The ordinary post: the worker prepares P8 once per follower, saving repeated author-timeline reads.
  • The ten-million-follower page: the handler reads its recent timeline only for active readers, avoiding a huge insert burst for people who never return.
  • The privacy rule: both routes must pass the same main-database audience, deletion and follow checks before returning content.
  • The operating cost: prepared lists require progress tracking, retry-safe inserts, stale-version rejection and repair. The hybrid also needs merging and a stable page view.

Measure page latency, candidates examined per returned item, age of pending follower updates, publication-to-candidate delay, scoring cost, duplicates, permission failures and fallback use. Engagement features may lag; forbidden content must still be excluded. Evaluate relevance with controlled experiments and satisfaction or harmful-content checks, because extra clicks or time spent do not automatically mean a better feed.

OPTIONAL DESIGN EXTENSIONWhere AI Fits in This Design

A model can optionally rank the posts already found for Mina. Finding possible posts and deciding which post may be read remain separate operations.

  1. Input and output. After checking access, the feed handler sends a bounded set of post IDs with recency, approved engagement counts and consented viewer preferences. The model returns a score for each supplied ID, not new post content. The TensorFlow Recommenders example separates candidate retrieval from this ranking step.
  2. Integration. The handler orders candidates by those scores, then loads selected display records and checks current versions and permission again. A high score cannot restore P8 after deletion or an audience change.
  3. Fallback. Assume a30 ms scoring budget within the 300 ms page target. If feature loading or inference misses it, use chronological order over currently permitted candidates. Batch feature requests and cap candidates so a model upgrade cannot create unlimited remote calls.
  4. Evaluation and privacy. Measure relevance, satisfaction, diversity, harmful-content regressions, response time and cost/page. Protect private interaction history and honor deletion and retention in training copies. Keep the model version with the page’s ranking context so an experiment does not silently change later-page ordering.

DRAW. CALCULATE. EXPLAIN.

Choose fanout policies and prove deletion safety

75 minutes · guided practice

Model three authors: 100 followers posting ten times/day, 20,000 followers posting twice/day, and ten million followers posting once/day. Compare eager write counts with pull work for 1% daily active followers. Design the read stages and a stable cursor. Then process create version 8 after delete version 9 and show why the post remains invisible.

Your self-check
Compare the worked solution
  1. Count prepared inserts. The authors need 100×10 =1,000, 20,000×2 =40,000, and 10,000,000×1 =10 million references/day respectively if every publication updates every follower’s list.
  2. Count active readers separately. At 1% daily activity, the first author has one active follower, the second has 200, and the third has 100,000. Pull cost also depends on pages per reader and timelines read per page, so these counts do not prove one universal follower threshold.
  3. Choose a hybrid. Prepare references for ordinary authors. For the huge page, read its recent timeline when an active viewer asks. The handler merges both ID sets, removes duplicates and checks current access before ranking a bounded set and loading display records.
  4. Continue a defined ranking. Return a cursor referring to a short-lived ranking view and stable post-ID tie-break. If it expires, require a restart instead of silently treating a new order as the old traversal.
  5. Reject old work after deletion. Retain delete version 9. A worker processing create version 8 compares against it and does not restore P8. A read also checks the source post and current audience, so candidate cleanup may lag without exposing a deleted body.
  6. Explain each saved record. The post is the current content; the outbox retains unfinished publication work; (viewerId,postId) prevents duplicate list inserts. None of those list entries is permission to read.

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 do push, pull and hybrid feeds differ?

Pull makes the feed handler read followed authors’ timelines when a viewer asks. Push makes a worker prepare follower references when an author publishes. A hybrid pushes ordinary authors and reads very large authors’ recent posts only for active viewers.

CARD 02What happens between a post ID and a displayed feed item?

The handler collects possible IDs, removes duplicates, checks current permission and scores a bounded set. It then loads display records, called hydration. An old feed entry or high score does not grant permission to return the body.

CARD 03How do deletion and pagination stay coherent?

The main post record retains newer versions and deletion state so a delayed worker cannot restore an old create. Page cursors continue a defined ranking view with an ID tie-break. When that view expires, restart explicitly.

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 / 10One million posts/day each fan out to 200 followers. How many references are written?
2 / 10Which source most strongly motivates pull in a hybrid feed?
3 / 10Where does hydration belong in the read path?
4 / 10Why use a unique viewer/post key for derived feed entries?
5 / 10Delete version 9 is processed before create version 8. What keeps P8 invisible?
6 / 10What does a five-second candidate-freshness target promise?
7 / 10Why is a live page number insufficient for stable ranked pagination?
8 / 10A cached feed entry points to a now-private post. What should the read do?
9 / 10Why estimate candidate evaluations in addition to page requests?
10 / 10The learned ranker misses its deadline but eligible candidates are available. Which response preserves the design?
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