System designby Learnastra

System-design interview · Core interviews

Design a personalized news feed

By Anup Rai

Commit posts before acknowledging publication, combine precomputed follower lists with author timelines, and rank a bounded set for each page. Keep pagination stable while rechecking current post visibility and relationships.

You will learn to

  • Build a feed from followed-author records on one server.
  • Choose precomputation versus read-time assembly using measured work.
  • Trace a post through durable publication, candidate caching, ranking, and permission changes.

Practice in this chapter

8 interview questions with model answers and follow-ups.

Go to interview practice

Useful foundations: Database indexes: B-trees, composite keys and query access · Caching: cache hits, misses, write policies and invalidation · Message queues, event logs, delivery guarantees, and backpressure · Real-time communication: polling, long polling, SSE, and WebSocket

Workload and timing examples are interview assumptions.

01Problem and scope

A personalized news feed combines eligible posts from followed people, pages and groups into a useful ordered page. Separate four responsibilities: publication stores the source post, candidate generation finds possible stories, ranking orders them, and delivery returns the selected content. For example, a twenty-story page can merge text, photos and videos from 500 followed entities. A correct single-server baseline queries recent author timelines and checks visibility before returning the page; precomputation is a later performance decision.

A materialized candidate list stores post IDs selected in advance for one viewer. Preparing it on publication distributes work across recipients, called fanout; assembling it during a feed read gathers posts from multiple authors, called fan-in. Both approaches still need eligibility checks and ranking before a response is returned.

Candidate generation, ranking and client delivery have different costs and failure modes. Materializing a server-side candidate list does not require an online client, and sending a socket notification does not make a source post durable. Keep those decisions explicit when comparing push and pull designs.

Include posts from followed people, pages and groups, with ranked order and explicit reply filtering. New publications may take a few seconds to reach candidate lists, but current eligibility must be checked when serving. Clarify pagination behavior when ranking or relationships change: this design freezes a bounded candidate ordering for the session while allowing current privacy rules to remove ineligible results.

The scaling choice is whether to combine author timelines on every read or write a post reference to many recipients when the author publishes. The protocol must also tolerate publication retries, cache loss and relationship changes during fanout. Candidate caches are recoverable performance structures; the post and relationship authorities decide what can be returned.

02Functional requirements

  1. Publish stories: Accept text with media references from people, pages and groups; apply explicit reply filtering.
  2. Manage relationships: Support follow/unfollow and enforce current post, block and group privacy.
  3. Read and refresh: Open a twenty-story page, request older eligible stories and refresh for newer ones.
  4. Remove ineligible stories: Deleted or newly restricted posts disappear from future authorized responses even if candidate caches still contain their IDs.
  5. Recover inactive feeds: Reconstruct a returning inactive user's feed instead of treating cache eviction as an empty feed.
  6. Preserve pagination: Freeze the chosen candidate order within a pagination session, except for current eligibility filtering; a new post arrives on refresh or a “new stories available” hint.

Active-reader timing and cold starts

Assume a two-second feed-request deadline and five-second active-reader freshness target. A five-minute periodic rebuild alone cannot meet five-second freshness; add incremental publication events. A user absent for months may wait for reconstruction rather than consuming the same resources as every active reader.

Unread stories and client delivery

Retain previously ranked but unseen stories only within a bounded age horizon; do not repeatedly reinsert every consumed story. Ranking may change between refreshes, without shifting every existing page boundary.

Pull-to-refresh is the client baseline. Active clients may receive lightweight WebSocket/long-poll hints. Preparing candidates while someone is offline does not mean transferring all stories to the phone; mobile clients may avoid unseen-content transfers.

Scope limits

Ads, full recommendation-model training and video processing are separate services.

03Non-functional requirements

  1. Response deadline: Use a two-second active-feed deadline, with an assumed p95 of 500 ms for ordinary active readers. At the deadline, return a valid smaller/fallback page or explicit failure; this does not guarantee every internet client receives it within two seconds.
  2. Freshness: Eligible publications become available within five seconds.
  3. Availability: 99.95% read availability. These are exercise targets, not measured product facts.
  4. Cold start: Returning inactive readers may wait longer for initial reconstruction under a separately documented target.
  5. Publication durability: Persist the source post and publication event before acknowledgment. Source posts and relationship changes need a declared durable failover policy; candidate caches may be lost and rebuilt.
  6. Regional recovery: Restore authoritative content, relationships, request identities and change-log positions before claiming current privacy checks.
  7. Retention and result size: Follow product policy for durable posts and relationship history; retain only the most useful 200–500 candidate IDs for active readers. Media retention is separate. Return fewer than twenty stories when too few eligible candidates fit the latency budget.
  8. Ranking quality: Evaluate useful interactions, diversity, undesirable-content exposure and user feedback—not engagement or p95 alone.

Visibility and degradation rules

Rule Required behavior
Duplicate fanout Asynchronous, at-least-once work must not create duplicate visible stories.
Current authorization Deletion, block and group-membership checks use authoritative eligibility at the serving check; a candidate-cache hit is insufficient.
Disclosure limit Already-delivered bytes cannot be recalled after a later permission change.
Incident fallback Reduce ranking complexity before access checks; an authorized chronological feed is acceptable.

A five-second freshness target and two-second active-request deadline are different contracts. Meeting p95 with irrelevant stale candidates does not meet the product objective.

04Capacity estimates

Workload assumptions and arithmetic

Assume 300M daily active readers, five reads/day, and 500 followed users/entities per reader.

Worked estimates

Quantity Calculation Implication
Feed requests 300M × 5 / 86,400 ≈ 17,361/s Plan peak headroom separately
Naive author fetches 17,361 × 500 ≈ 8.68M/s Read-time assembly can dominate
Full cached feeds 300M × 500 × 1 KB = 150 TB Repeated bodies are expensive
IDs only 300M × 500 × 8 B = 1.2 TB Share bodies/media elsewhere

Capacity implications and limits

IDs still need score/order metadata, allocator space, and replicas. If most readers consume ten pages of twenty stories, retaining 200 candidates may suffice; older requests can use durable history. Tune active-user eviction and pre-generation using observed access patterns.

At a fivefold peak, expect about 86,805 feed requests/s. Returning twenty 1 KB story summaries is about 1.74 GB/s before media; photos/videos should be referenced and delivered through the media system rather than duplicated into feed rows. A 500-candidate lightweight ranking pass at that peak scores roughly 43.4 million candidate-viewer pairs/s, motivating a cheaper first stage and bounded candidate pools.

Assume an ordinary author has 500 followers and 40% are active in the precompute window: one post causes about 200 candidate writes. An author with 20M followers and the same active fraction causes 8M writes per post. At 40 bytes/candidate entry, that is 320 MB of logical candidate mutations before replicas, network and index overhead. If they post 100 times/day, eagerly distributing every post can be much more expensive than retrieving their recent timeline only for actual readers.

The threshold should compare work: publication rate × eligible active followers × candidate-write cost versus active feed reads that would need that author's timeline × pull/merge cost. Follow count alone is a useful first heuristic, but active fraction and posting frequency change the break-even point.

05APIs and contracts

Request and response example

POST /posts
Idempotency-Key: k91
{text:"Trail report", mediaIds:[m4], visibility:"friends"}
→ {postId:p882,version:1,status:"published"}

GET /feed?limit=20&excludeReplies=true&cursor=f18
→ {stories:[...],nextCursor:f19,session:s7,newerAvailable:true}

The server derives the author's identity from authentication. Reusing k91 with the same payload returns p882; a different payload conflicts. Media IDs must refer to uploads the author may attach. Publish acknowledgment means durable source state, not immediate presence in every follower's feed.

The opaque cursor binds viewer, session, filter set, ranking version and last position. A chronological feed can use (createdAt,postId); a ranked feed needs a frozen candidate ordering or stable score context. since_id and max_id are valid chronological shortcuts only if the chosen ID scheme has the required order. An expired session asks the viewer to refresh rather than inventing an inconsistent continuation.

Relationship changes return a committed relationship version. Unfollow and block events help clean caches, but the serving path checks authority even before cleanup finishes. Internal events contain event ID, post/relationship version and source watermark—the source-log position used to track which changes have been processed. Workers acknowledge batches only after their progress or candidate mutations can be safely replayed. Read APIs cap page size and candidate expansion, preventing a request for an unlimited historical feed.

06Data model and access patterns

The records connect the publication path to one viewer’s feed: a Post holds source content, a Follow identifies a potential source, and a candidate-cache entry records a post that may be considered for that viewer. The cache entry stores a reference and ranking metadata; it does not replace the post or grant access to it.

Interface/record Example
Publish POST /posts {idempotencyKey:k91,text:...,mediaIds:[m4]}
Feed request GET /feed?limit=20&cursor=f18&excludeReplies=true
Relationship Follow(viewer=u31,target=u17,type=user,version=6)
Post Post(p882,author=u17,entity=null,createdAt=900,visibility=friends)
Candidate cache u31 → [(p882,score=7.2),...], watermark=e301

Separate User, Entity, Follow, Post, and PostMedia relations. Photos/videos live in object storage, delivered through a content distribution layer. Index author timelines by (authorId,createdAt,postId) for bounded retrieval. since_id/max_id are useful only when ID ordering matches the chosen chronology; ranked feeds need score and snapshot context in their opaque cursor.

Store a unique (viewerId,postId) candidate identity with insertion provenance, source version and generation. This supports idempotent upsert and removal without storing another body copy. An ordered structure serves iteration, while a hash lookup supports fast duplicate checks and deletion; a linked map helps these operations, but arbitrary ranking requires an ordering mechanism too.

Post and author timeline are authoritative for content creation; relationships and group membership are authoritative for eligibility. Candidate feeds, rank-feature caches, body caches and notification hints are derived. Partition candidate lists by viewer ID for local page retrieval, while author timelines use (authorId,createdAt,postId) and posts use their own ownership key. The social graph has both following and follower access patterns; materialize reverse edges carefully rather than scanning every viewer during publication.

A feed session stores a bounded list/order or a reproducible snapshot context with expiry. Record enough rank-model/feature version to explain why continuation is stable. Do not retain every session forever; its resource budget is distinct from the persistent user's candidate list.

Keep distribution relationships separate from access grants. Following a public author makes their posts eligible for this followed-content feed; unfollowing removes that source from future feed responses but does not make the author’s public profile secret. Friends-only posts require the product’s approved friendship relation, and private-group posts require current group membership. Store those relationship types/statuses explicitly. The worked race uses follower-only eligibility; do not silently use an unapproved one-way follow as permission to read friends-only content.

07Basic working design

Relational source and follow graph

The first system has one application and a relational database containing users, entities, follows, posts and media references. The viewer follows 500 targets. On a feed request, the application loads those IDs, obtains a bounded recent set from each indexed author timeline, filters current eligibility and replies, sorts by time and returns twenty stories. This is a complete working design for a modest product.

Publication and pull-on-read assembly

The author publishes p882. The transaction inserts the post, author-timeline entry and a durable publication event before returning success. The baseline does not need fanout for correctness: the viewer's next read can query the author's timeline directly. If the post response is lost, k91 returns the existing p882 rather than duplicating it.

Stable newest-first pagination

The initial ranking is newest first with a stable post-ID tie-breaker. A session records the cutoff and order context so new posts do not shift older pages. Media bodies remain outside the database; the response carries appropriate authorized references. Delete and unfollow are checked during reads.

When the baseline remains sufficient

This baseline supports durable publication and authorized feed reads; its main scaling cost is repeated timeline retrieval. It avoids the operational burden of millions of precomputed lists until repeated fan-in proves expensive. It also provides the reconstruction path when later caches fail.

architecture · baselineBaseline: assemble followed timelines on read

The simple design is correct and reconstructable; repeated fan-in is its scaling cost.

Baseline: assemble followed timelines on readThe simple design is correct and reconstructable; repeated fan-in is its scaling cost. client to api: Publish p882 / request feed; api to db: Commit post; query followed timelines; api to db: Check current eligibility; hydrate; api to client: Return ordered stories and cursor; client to media: Fetch authorized mediaPublish p882 / request feedCommit post; query followedtimelinesCheck current eligibility;hydrateReturn ordered stories andcursorFetch authorized mediaACTORPublishing / readingclientsSERVICEFeed applicationSTOREPosts / follows /timelinesEXTERNALMedia object deliverysync
Read each connection in order
  1. syncPublish p882 / request feedPublishing / reading clients → Feed application
  2. syncCommit post; query followed timelinesFeed application → Posts / follows / timelines
  3. syncCheck current eligibility; hydrateFeed application → Posts / follows / timelines
  4. syncReturn ordered stories and cursorFeed application → Publishing / reading clients
  5. syncFetch authorized mediaPublishing / reading clients → Media object delivery

08Find the baseline flaws

Bottleneck / counterexample Evidence and design consequence
Multi-author read amplification At 17,361 average feed requests/s and 500 followed targets, the naive path attempts approximately 8.68 million author-timeline fetches/s. A fivefold peak reaches 43.4 million. Even batched queries must inspect and merge a large candidate set; the two-second objective becomes fragile when one timeline or graph lookup stalls.
Unbounded celebrity writes A tempting fix is fanout to every follower on every publish. An ordinary author’s 500 followers are manageable, but a 20M-follower author can consume millions of writes for readers who will not open the application. Another tempting fix is a five-minute periodic feed rebuild: it cannot satisfy the five-second publication-freshness target no matter how fast the cache reads are.
Unfollow racing late fanout Now test correctness. Worker W reads the viewer's follow version 6, then pauses. The viewer unfollows the author, committing version 7. W resumes and inserts p882 into the candidate cache. If the reader trusts cache membership as authorization, it returns an ineligible story. Deleting the entry asynchronously reduces clutter but cannot close this race by itself.

These failures motivate combining push and pull, saving a recoverable event for each publication, and checking current permissions before returning stories. Each change solves a specific counterexample. A separate ranking service is not a cure for a missing post event or an unfollow race; those are data-lifecycle and eligibility problems.

09Improve the design, step by step

Fanout means distributing one publication to many recipients. A popular author with an illustrative 20M followers makes one post create 20M candidate writes, even if few followers read today.

Strategy Cheap path Expensive path
Generate on read Author publishing Follow-list queries and merges
Generate on write Common feed reads Many recipient writes and inactive feeds
Hybrid Ordinary reads/writes Two paths and deduplication

Use follower count, active fraction, author posting rate, and expected reads to choose the threshold. Cache ordered candidate IDs with fast ID lookup and a generation watermark; a linked map supports removal and iteration, but arbitrary ranking also needs ordering support. Precomputation can occur while the viewer is offline; long polling/WebSockets or periodic fetch govern delivery separately.

  1. Change 1 — precompute references for active ordinary followers. Trigger: 500-way repeated read assembly. A publication event inserts p882 into active recipients' candidate lists once. Normal reads become a bounded candidate fetch. Costs are write amplification and eviction/rebuild policy; delayed workers create freshness lag. Keep pure read generation for a small or mostly inactive user base.

  2. Change 2 — pull high-fanout authors during reads. Trigger: celebrity fanout consumes more work than it saves. The reader merges cached ordinary candidates with recent posts from selected pull-only author timelines. This bounds publication amplification but adds read fan-in and two-path deduplication. When an author switches strategies, record the source-log position where the change applies. Overlap both paths around that position and remove duplicate IDs, so no post falls between them or appears twice. Pure write fanout is still reasonable when nearly every follower reads and posting is rare.

The publication event already committed by the baseline becomes the input to asynchronous fanout. An outbox is a database record written in the same transaction as the post, then relayed to the event system. This avoids a gap where the post commits but a failed separate queue send leaves fanout with no record of it.

  1. Change 3 — durable event and generation recovery. Trigger: worker crashes and lost caches. A source outbox/log records publication; idempotent (viewer,post) upserts and durable progress let workers replay. A cache generation is reconstructed from authoritative timelines and relationships, then catches up from a recorded watermark. This adds logs, retention and reconciliation cost; an event gap beyond retention requires a broader rebuild. Periodic repair complements, but cannot replace, incremental freshness.

  2. Change 4 — staged ranking with current eligibility. Trigger: scoring hundreds of candidates at peak dominates CPU and stale candidates risk leakage. Cheap filters reduce the pool before expensive ranking; current access checks and final post-body loading, called hydration, determine what may be returned. This saves compute and keeps privacy independent of cache lag. The new risk is lost recall from overly aggressive candidate pruning, so evaluate quality as well as latency. Chronological ordering remains a useful simpler product or incident fallback.

10Detailed architecture

Publication log and candidate generation

The publication API commits content and outbox state in the post authority. An event relay feeds a durable log. Fanout workers read follower/active-user information and write viewer-partitioned candidate references for ordinary authors; high-fanout authors retain authoritative timelines that readers pull directly. A strategy/version configuration tells both paths how to overlap safely during changes.

Authorized ranked serving

The serving API retrieves the viewer's candidate list and bounded recent pull-author timelines, deduplicates IDs and applies cheap eligibility filters. A ranking service combines agreed features into a useful order, then a hydration/visibility service checks current authoritative eligibility and fetches current bodies. Media delivery uses its own access contract and content-distribution layer. The final diagram includes these checks rather than drawing a cache directly to the client.

Relationship access patterns

Relationship storage must support different queries: publication needs the author’s followers; a feed read needs the viewer’s current follows, blocks and group memberships. A graph cache can accelerate reads only within an explicitly safe revocation policy. TAO is useful primary background on social-graph service design, not evidence that this exact architecture is used by a named company.

Notifications are hints

Push notification gateways only announce newer stories or session events. They do not guarantee publication durability and are not the feed store. During a notification outage, the viewer can still pull an authorized feed. During a ranking outage, an authorized chronological fallback can still work; during uncertain access control, private stories must be withheld.

Concrete source, log and cache choices

A coherent implementation starts with transactional SQL for posts, author timelines, request identities and an outbox; a durable event stream carries publication changes; a Redis-style ordered cache can hold disposable viewer candidates. A durable session store retains the bounded chosen order for cursor lifetime. A graph service becomes useful when relationship access patterns justify it, not merely because the product is social. Kafka consumer progress alone does not atomically update an external candidate cache, so generation recovery and idempotent viewer/post effects remain application duties.

architecture · finalFinal: hybrid candidates with current eligibility

Fanout and pull paths meet before ranking. Current authority gates output even when candidates are stale.

Final: hybrid candidates with current eligibilityFanout and pull paths meet before ranking. Current authority gates output even when candidates are stale. client to publish: 1a. Publish k91 / media references; publish to posts: 2. Commit p882 + outbox; posts to log: 3. Relay committed event; log to fanout: 4. Consume publication; fanout to graph: Page followers and active recipients; fanout to candidate: 5. Idempotent viewer/post upsert; fanout to notify: New stories hint; notify to client: Optional lightweight update; client to read: 1b. Feed request / cursor; read to candidate: 6. Ordinary-author candidates; read to posts: 7. Pull high-fanout timelines; read to rank: 8. Deduplicate and score candidates; rank to hydrate: 9. Ranked candidate IDs; hydrate to graph: Current eligibility / membership; hydrate to posts: Current post versions and bodies; read to session: 10. Freeze / resume bounded order; hydrate to read: 11. Authorized stories; read to client: 12. Page and cursor; client to media: Fetch permitted media bytes1a. Publish k91 / mediareferences2. Commit p882 + outbox3. Relay committed event4. Consume publicationPage followers and activerecipients5. Idempotent viewer/postupsertNew stories hintOptional lightweight update1b. Feed request / cursor6. Ordinary-author candidates7. Pull high-fanout timelines8. Deduplicate and scorecandidates9. Ranked candidate IDsCurrent eligibility / membershipCurrent post versions andbodies10. Freeze / resume boundedorder11. Authorized stories12. Page and cursorFetch permitted media bytesACTORReader / authorclientsG4SERVICEPost publication APIG1STOREPost authority /author timelinesG1QUEUEPublication changelogG1WORKERBatched fanoutworkersG2STOREFollow / group / blockauthorityG3CACHEViewer candidatepartitionsG2SERVICEFeed assembly APIG3SERVICEStaged ranker /feature serviceG3SERVICECurrent eligibility /hydrationG3STOREFeed session orderstoreG3SERVICENew-storynotification gatewayG2EXTERNALAuthorized mediadeliveryG4syncasyncG1 Publication authorityG2 Derived candidate generationG3 Serving and authorizationG4 Client and media boundary
Read each connection in order
  1. sync1a. Publish k91 / media referencesReader / author clients → Post publication API
  2. sync2. Commit p882 + outboxPost publication API → Post authority / author timelines
  3. async3. Relay committed eventPost authority / author timelines → Publication change log
  4. async4. Consume publicationPublication change log → Batched fanout workers
  5. syncPage followers and active recipientsBatched fanout workers → Follow / group / block authority
  6. async5. Idempotent viewer/post upsertBatched fanout workers → Viewer candidate partitions
  7. asyncNew stories hintBatched fanout workers → New-story notification gateway
  8. asyncOptional lightweight updateNew-story notification gateway → Reader / author clients
  9. sync1b. Feed request / cursorReader / author clients → Feed assembly API
  10. sync6. Ordinary-author candidatesFeed assembly API → Viewer candidate partitions
  11. sync7. Pull high-fanout timelinesFeed assembly API → Post authority / author timelines
  12. sync8. Deduplicate and score candidatesFeed assembly API → Staged ranker / feature service
  13. sync9. Ranked candidate IDsStaged ranker / feature service → Current eligibility / hydration
  14. syncCurrent eligibility / membershipCurrent eligibility / hydration → Follow / group / block authority
  15. syncCurrent post versions and bodiesCurrent eligibility / hydration → Post authority / author timelines
  16. sync10. Freeze / resume bounded orderFeed assembly API → Feed session order store
  17. sync11. Authorized storiesCurrent eligibility / hydration → Feed assembly API
  18. sync12. Page and cursorFeed assembly API → Reader / author clients
  19. syncFetch permitted media bytesReader / author clients → Authorized media delivery

11Write path and acknowledgement

Publication commits the source post and event before asynchronous candidate fanout begins. The example uses request k91, post p882, media m4, event e301 and viewer u31.

  1. The author sends k91 and media reference m4. The post service validates ownership, commits p882 version 1, its author-timeline entry, replay result and outbox e301, then acknowledges publication.
  2. A relay publishes e301 to the durable log. If its reply is lost, it republishes the same event identity; downstream work tolerates duplicates.
  3. The fanout worker resolves the author's strategy and reads follower pages plus active-user filters. It processes bounded batches with a persisted cursor, rather than loading millions of followers in one allocation.
  4. For the viewer, it upserts (u31,p882) into the current candidate generation with source version and e301 provenance. Repeated work updates or returns the same record; it does not append duplicate story slots.
  5. The worker checkpoints recipient progress only after the batch is durably recoverable. On crash, it may replay that batch. Publication-to-candidate lag is measured from p882's commit timestamp.
  6. A lightweight event may tell the viewer that newer stories exist. The feed response still comes through candidate merge, current eligibility, ranking and hydration.

If the author is pull-only, the source post and author timeline commit are enough for discovery; no enormous recipient loop is required. During strategy migration, a defined overlap window may use both paths, relying on post-ID deduplication. It is safer to briefly duplicate candidate work than to create a gap where neither path includes p882.

12Read and delivery path

A feed read operates on candidate IDs rather than trusting cached bodies or permissions. This path assembles one twenty-story page, binds its cursor to a session, and authorizes the exact content versions returned.

  1. An authenticated viewer requests a twenty-story page. Validate the cursor’s viewer, filters, session expiry and ranking version; an initial request creates a new bounded session.
  2. Load ordinary-author candidate IDs and recent posts from followed pull-only authors. Share one in-progress cache reconstruction among concurrent requests for the same viewer rather than having each request scan every author.
  3. Deduplicate post IDs, apply reply filters and fetch current eligibility from the relationship/post authority. Bind each permitted candidate to its exact content version and policy revision.
  4. Rank the bounded eligible set under the session’s ranking policy. Hydrate the authorized immutable versions and omit or reauthorize any mismatched version within the deadline.
  5. Persist the session order or equivalent continuation context and return up to twenty stories with cursor f19. Current eligibility may shorten later pages without changing the remaining order.
  6. Deliver media through its access-enforcing path. A separate notification can announce newer stories; a refresh starts a new session rather than inserting them into an existing page boundary.

Reliable logs help publication survive worker retries; they do not remove the need for idempotent insertion. Kafka design.

For a concrete ranked session, the reader collects 300 ordinary candidates plus 100 from pull-only authors, removes duplicate IDs, filters disallowed replies and performs cheap current-eligibility checks. A lightweight ranker selects 100 for a more expensive scorer, then diversification rules produce the twenty-story page. The numbers are illustrative budgets to evaluate, not assumed universal model architecture.

The authorization result names the permitted immutable post version and content-policy revision. Hydration fetches that exact version, never a newer body under the older decision. If only a current-body API is available, compare its version and policy revision with the authorization result; on mismatch re-authorize the returned version, and omit the candidate if that bounded retry fails. If p882 was deleted before this serving check, skip it and fetch bounded replacements. Persist the session order or equivalent stable context before returning f19. Page two resumes that session; refresh creates a new one that can include later publications.

For a cache miss, coalesce concurrent reconstruction for the viewer rather than making every request independently scan 500 timelines. Apply a deadline and return a smaller valid chronological page if full personalized ranking cannot finish. The response states any fallback; it never labels stale candidate text as authorized merely to fill twenty positions.

13Correctness deep dive

Ranking decides which eligible posts are most useful; authorization decides which posts may be shown at all. The scoring example establishes that ordering policy, then the unfollow race tests the separate access decision and its connection to the exact body returned.

Ranking features after bounded retrieval

Begin with time ordering, then explain features: affinity to the author (an estimate of the viewer’s interest based on prior interactions), topical relevance, likes/comments/shares, age, and media type. Bound candidates before expensive scoring. Evaluate usefulness and retention alongside latency and inappropriate-content exposure; engagement is not automatically quality.

From signals to a ranking decision

Raw signals such as author affinity are model inputs. Predictions estimate outcomes for this viewer; a scoring policy combines those predictions. For an illustrative interview policy, let score = 2×P(meaningful interaction) + 0.5×P(save) + 0.1×freshness − P(hide), where P denotes the model’s predicted probability for that outcome and freshness is normalized to 0–1. These weights are assumptions, not a named company's production formula.

Eligible post Predicted interaction / save / hide; freshness Score
p882 0.30 / 0.10 / 0.02; 0.80 0.60 + 0.05 + 0.08 − 0.02 = 0.71
p883 0.15 / 0.40 / 0.01; 0.90 0.30 + 0.20 + 0.09 − 0.01 = 0.58

The first post wins despite being less fresh. Break equal scores by a stable post ID, then apply diversity rules, such as limiting consecutive posts from one author. Freeze the resulting order for the page session; current eligibility can still remove a post. Check calibration—whether events assigned a given probability occur at about that rate—as well as satisfaction and unwanted-content exposure before trusting the scoring objective. Meta's published ranking explanation illustrates signals, predictions, combined scores and later contextual ranking; this small example is an interview model, not a reproduction of that system.

Candidate and source partitioning

Partition candidate feeds by viewer ID; partition author timelines/posts for their own access patterns. Replicate hot read data. Social-graph storage is itself a major workload, as the TAO research system illustrates; that paper does not prescribe this exact feed architecture. TAO paper. Consistent hashing helps remapping, while redundant copies and replay provide recovery.

Unfollow interleaves with delayed fanout

Consider the actual interleaving. W reads relationship version 6 and schedules p882 for the viewer. The relationship authority then commits unfollow version 7 and returns success. W's late candidate insertion succeeds because candidate storage is a derived performance structure. When the viewer's next request reaches the serving authorization point, a current read observes version 7 and rejects the author's follower-only content.

serve(viewer, candidateIds, session):
  candidates = deduplicate(candidateIds)
  eligibility = currentAuthorityCheck(viewer, candidates)
  eligible = candidates where eligibility.allows(postId)
  ranked = rankUnderSessionPolicy(eligible)
  for post in ranked:
    decision = eligibility[post.id]  # allowed postVersion + policyRevision
    body = fetchImmutableVersion(post.id, decision.postVersion)
    if body.version != decision.postVersion
       or body.policyRevision != decision.policyRevision:
         retry authorization for this candidate, within deadline
         otherwise omit it
    else: append body to response

Authorization boundary decides the result

Bind authorization to the content version

Relationship-version hints do not authorize

The worker can tag the candidate with relationship version 6 and consumers can eagerly remove it, but neither replaces current policy enforcement. A blocked user, deleted post or restricted group follows the same reasoning. Ranking cannot override eligibility because “high predicted engagement” is not an access right. Private media URLs also need bounded authorization semantics; a long-lived public object URL would defeat a correct feed-body check.

Why independent rebuilding is safe

This proof shows why cache rebuilding and candidate ordering are safe to be eventually consistent while permission decisions have a stronger serving requirement.

sequence · unfollow-raceLate fanout after a committed unfollow

Cache membership remains derived; current serving authorization decides whether a story can be returned.

Late fanout after a committed unfollowCache membership remains derived; current serving authorization decides whether a story can be returned. worker to graph: Read the viewer follows the author / v6; graph to worker: Eligible at v6; graph to graph: Commit the viewer unfollow / v7; worker to cache: Late upsert p882 using old v6; reader to cache: Read candidate p882; reader to graph: Current eligibility for p882; graph to reader: v7: not eligible; reader to reader: Drop p882; rank other candidatesPARTICIPANTFanout workerPARTICIPANTRelationshipauthorityPARTICIPANTCandidate cachePARTICIPANTFeed reader1. Read the viewer followsthe author / v62. Eligible at v63. Commit the viewerunfollow / v74. Late upsert p882 using old v65. Read candidate p8826. Current eligibility for p8827. v7: not eligible8. Drop p882; rank othercandidatessyncreturn
Read each connection in order
  1. syncRead the viewer follows the author / v6Fanout worker → Relationship authority
  2. returnEligible at v6Relationship authority → Fanout worker
  3. syncCommit the viewer unfollow / v7Relationship authority → Relationship authority
  4. syncLate upsert p882 using old v6Fanout worker → Candidate cache
  5. syncRead candidate p882Feed reader → Candidate cache
  6. syncCurrent eligibility for p882Feed reader → Relationship authority
  7. returnv7: not eligibleRelationship authority → Feed reader
  8. syncDrop p882; rank other candidatesFeed reader → Feed reader

14Failure and recovery

Failure / interleaving Required response and recovery
Late fanout after unfollow The worker reads the viewer’s follow version 6. The viewer unfollows the author, producing version 7, before p882 is inserted. Deleting that cache entry eventually is useful, but the decisive protection is checking current eligibility when serving. The same reasoning applies to blocks, deleted posts, and restricted groups.
Lost cache or delayed events If the cache disappears, reconstruct from durable posts and relationships; coalesce concurrent rebuilds to avoid a flood. If events lag, prioritize active readers or temporarily retrieve more authors during reads. Track publication-to-feed lag, fanout amplification, cache hits, duplicate/empty pages, permission-filter rate, and p99. A lost cache is recoverable; an unrecorded publication event needs reconciliation.
Partial fanout and generation recovery If fanout e301 commits to half its recipient batches and the worker crashes, resume its durable cursor or replay idempotent upserts. Never checkpoint the full follower list before the mutations are recoverable. If a cache partition disappears, rebuild a new generation from current relationships and recent author timelines, then replay events after its start watermark before publishing it.
Ranking or serving overload Under overload, prioritize active-reader publication, cap celebrity pull fan-in and reduce expensive ranking stages. Keep write queues bounded and expose freshness degradation rather than letting hours of backlog accumulate unseen. A ranker timeout can fall back to time ordering; a graph-permission timeout cannot safely fall back to public-looking cached text.
Revocation and lost notifications When a group removes the viewer while a media response is already in flight, previously delivered content cannot be recalled. Short-lived signed URLs reduce future access windows, but strict immediate revocation needs a checking proxy or other access-enforcing delivery design. State that residual limitation. Notifications may be duplicated or lost; reconnect reads current feed/session state, not the last socket's memory.

Rebuild without a publication gap

A cache generation is one identifiable reconstruction of a viewer’s candidate list. Publications can continue while it is being built, so the rebuild needs both a timeline scan and replay of changes recorded during the scan. The sequence below establishes that overlap and an explicit handoff to the new generation.

15Operations, security, and cost

Freshness, eligibility and latency signals

Monitor publication-to-eligible-feed lag against five seconds, feed p95/p99 against the response budget, candidate writes per post, active-recipient fraction, cache rebuild rate, feature latency and final permission-filter rate. Track duplicate IDs and unexpectedly empty pages as product defects. A spike in filtered candidates may reveal delayed unfollow/delete cleanup or a stale graph replica, not merely harmless cache waste.

Candidate-memory cost

At 300M active readers and 200 retained IDs each, eight-byte IDs alone consume 480 GB; at 40 bytes with order/provenance metadata, logical state is about 2.4 TB before replicas and runtime overhead. Three copies exceed 7.2 TB. Caching full 1 KB stories at that depth would be 60 TB logical and repeatedly duplicate media metadata. Store references and share bodies.

Measure fanout-threshold economics

Before changing the fanout threshold, calculate its cost on recorded traffic: how many candidate writes would it save, and how many extra author timelines would each reader fetch? Evaluate ranking changes on offline judgments and online user metrics with guardrails for diversity and harmful exposure. Engagement alone can optimize the wrong outcome.

Watermarked rollout and deletion tests

For each strategy change, record the version and source-log position, then overlap the old and new candidate paths until the new path covers that position. Test worker crashes after a recipient batch, cache loss during a rebuild, an unfollow before a delayed insert and deletion during pagination. Load tests must include high-degree authors and reconnecting inactive users, not only uniformly distributed ordinary accounts.

16Decision ledger and limitations

Choice Benefit Cost/limit Revisit when
Active-user reference fanout Fast ordinary reads Candidate-write amplification Most followers are inactive or author posting rate rises
Pull-only high-fanout authors Bounded celebrity publication work Extra read fan-in Nearly all followers read every rare post
Bounded candidate sessions Stable pagination and predictable work New stories require refresh Product wants an explicitly live reshuffling stream
Current eligibility at serve time Safe stale-cache handling Graph/post reads on response path A proven revocation-aware alternative exists
Staged ranking More useful ordering within budget Candidate recall and model operations Chronological feed meets the product better

An ordered candidate cache is neither the complete historical feed nor the source of truth. Requests beyond its retained depth can query durable timelines, with a slower bounded contract. Social distance, affinity, age, likes/comments/shares and media preference are possible features; each introduces freshness and evaluation choices. Quality does not follow automatically from adding a machine-learning service.

The remaining bottlenecks are celebrity pull traffic, graph lookups and scoring at peak. Replica placement and healthy load-aware routing improve capacity, but consistent hashing alone does not provide replication or remove a hot author. The design's claim is an explainable balance under the stated read/write distribution, not that hybrid fanout is universally optimal.

17Interview closing

“I separate durable publication, candidate generation, ranking and delivery. The author's post and outbox event commit before acknowledgment. Ordinary authors fan out references to active followers; high-fanout authors are merged from recent timelines during reads. The viewer's candidate list is bounded and recoverable, and pagination uses a stable session. Before returning content, the serving path checks current eligibility and hydrates the exact authorized content versions, so a delayed fanout after an unfollow does not authorize the story.

“This trades candidate writes for cheaper repeated reads, while the hybrid avoids millions of unnecessary celebrity updates. The costs are two paths, deduplication, event lag and graph checks. I can fall back to an authorized chronological feed when ranking is slow, but never bypass privacy to meet latency. My next measurements are publication lag, candidate writes per consumed story and p99 feed latency as the number of author timelines read per request increases.”

If the interviewer adds recommendations from unfollowed creators, add a separately evaluated retrieval source and combine it with followed candidates under the same eligibility and ranking budget. If the requirement becomes strict chronological order with no personalization, remove unnecessary model stages and simplify the cursor. The architecture should respond to the requirement rather than preserve impressive-looking boxes.

Practise the interview questions

Say your answer aloud before opening the model answer. Then answer the follow-up and compare the reasoning.

Foundation · Question 1

How would you build a first working feed from followed people, pages and groups on one machine?

Reveal a model answer

I would store posts and follows, index each author’s timeline, and query a bounded recent list from every followed entity. I merge candidates, filter current visibility and replies, then sort and return a bounded page. This read-time baseline reveals the repeated graph lookups and merge work that later precomputation must save. Personalized ordering adds viewer features and a stable session context, not a new source of post ownership.

What the answer must demonstrate: Start with records and a working query.

Foundation · Question 2

Does fanout-on-write require every follower to be connected?

Reveal a model answer

No. It writes references into server-side candidate lists. The viewer can open the device later and read that list. A WebSocket notification is a separate delivery optimization and is not necessary to materialize an offline user’s feed.

What the answer must demonstrate: Separate materialization from client transport.

Applied · Question 3

Should a page with twenty million followers fan out every post?

Reveal a model answer

I would compare its publication rate times active followers with expected read-time retrieval cost. Usually I keep its author timeline and merge recent posts when a follower reads, while ordinary authors use candidate fanout. The threshold is a workload decision.

What the answer must demonstrate: Explain both cost and transition behavior.

Applied · Question 4

An unfollow commits while a worker is inserting an older follower-only post into that viewer’s candidate cache. Can the next feed response include it?

Reveal a model answer

If unfollow committed before the response’s authoritative eligibility check, the post is ineligible even if the worker inserted its ID afterward. Candidate membership is derived state. I check current relationship and visibility, bind the decision to the allowed content version, and hydrate that exact version. Cleanup removes stale candidates for efficiency; it is not the access guarantee.

What the answer must demonstrate: Cached membership is not permission.

Follow-up · Question 5

All candidate caches for a region are lost. Is the feed data gone?

Reveal a model answer

The precomputed views are gone, but durable posts, follow relationships, and events can rebuild them. I would prioritize active readers, coalesce requests for the same viewer, and serve a bounded read-time feed while reconstruction catches up. I record source-log positions before scanning, replay overlapping events into a new generation, then fence the old writer and publish the new generation with its resume position. A scan followed by a later subscription would leave a publication gap.

What the answer must demonstrate: Identify derived state versus source state.

Follow-up · Question 6

Can a five-minute scheduled rebuild meet five-second freshness?

Reveal a model answer

Not by itself. I would use incremental events for new candidate insertion and reserve scheduled rebuilds for reconciliation or reranking. I then measure publication-to-eligible-feed latency, not only the time taken to answer a cached read.

What the answer must demonstrate: Latency and freshness are separate measurements.

Applied · Question 7

An author changes from push fanout to pull-only candidate generation while new posts are being published. How do you avoid a coverage gap?

Reveal a model answer

I version the strategy and choose a publication watermark for the transition. Readers temporarily merge both the existing inbox candidates and the author timeline over a defined overlap window, deduplicating post IDs. I retire the old path only after the new path covers the watermark and older required candidates remain reachable. A flag flip independently observed by workers and readers can leave a period when neither path includes a post.

What the answer must demonstrate: Explain the transition protocol as well as the steady-state threshold.

Follow-up · Question 8

Does an unfollow erase a story already in an in-flight response?

Reveal a model answer

No. Define the authorization point: a committed unfollow before the current eligibility check excludes the story; a later change cannot recall bytes already authorized and sent. Future checks observe the new relationship. Stronger in-flight revocation requires extra coordination.

What the answer must demonstrate: State the temporal and media boundaries honestly.

Blank-page exercise · 45 minutes

Build the answer yourself

Design a personalized twenty-story feed from followed people, pages and groups. Compare a read-time baseline with hybrid candidate fanout, introduce a twenty-million-follower author, and resolve an unfollow that commits while a fanout worker is delayed.

  • Show the initial follow/post query.
  • Compute naive reads and full-body versus ID cache size.
  • Trace one post through durable publication, candidate generation and authorized retrieval.
  • Distinguish candidate generation, ranking, and transport.
  • Resolve the unfollow race and cache recovery.

Check that each component and design decision follows from your requirements and workload.

Recall the key ideas

Answer from memory before opening each card. Explain why the choice works and what it costs. Revisit missed cards tomorrow.

Design a personalized news feedWhat does fanout copy?Recall first, then reveal

Usually a post reference into many server-side candidate lists, not the entire media file to every phone.

One post, many references.

Return to lesson
Design a personalized news feedWhat is hybrid generation?Recall first, then reveal

Precompute ordinary authors for active followers and retrieve expensive high-fanout authors during reads.

Write the common work; read the exceptional work.

Return to lesson
Design a personalized news feedIs a feed cache an access-control decision?Recall first, then reveal

No. Current visibility, blocks, and group membership still govern delivery.

Candidate does not mean permitted.

Return to lesson

Final revision

Summary and interview notes

A personalized feed separates authoritative publication and relationships from recoverable candidates, ranking and delivery. Hybrid generation reduces repeated read assembly without turning cached candidate membership into a permission decision.

Remember these points

  • Publication commits source content, its author timeline and an outbox before acceptance.
  • Ordinary-author fanout writes references for useful active readers; high-fanout sources may be cheaper to pull.
  • Personalized ranking turns signals into outcome predictions and an explicit scoring policy, then applies diversity rules. Pagination freezes a bounded resulting order while current eligibility can remove stories.
  • Before rebuilding, record where event replay will start. Scan timelines, replay intervening events, stop old writers, then publish the new list with the position where processing resumes.
  • Following, friendship and private-group membership have different eligibility and access semantics.

Interview tips

  • Calculate candidate writes per publication and author reads per feed request before choosing a threshold.
  • Trace an unfollow that commits before a delayed candidate insertion and identify the serving authorization point.
  • Explain a strategy migration and cache rebuild, not only the steady-state hybrid diagram.

Important qualifications

  • A response authorized before a later revocation may finish; permanent public media URLs can bypass a private-feed contract.
  • An external cache is not made transactionally consistent by a Kafka offset commit.
  • The active-feed deadline and inactive-reader reconstruction objective are separate service contracts.

Technical references

  • TAO research paperPrimary description of a large social-graph data service; background for relationship access patterns.
  • Kafka designDurable log, consumer progress, and processing semantics underlying reliable incremental publication.
  • Meta: News Feed rankingPrimary 2021 explanation of candidate inventory, prediction models, combined ranking scores and contextual diversity; the chapter weights and example values are hypothetical.

Practice marks stay in this browser.