System-design interview · Core interviews
Design a personalized news feed
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 practiceUseful 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.
Dotted concept links open the relevant explanation in a new tab.
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
- Publish stories: Accept text with media references from people, pages and groups; apply explicit reply filtering.
- Manage relationships: Support follow/unfollow and enforce current post, block and group privacy.
- Read and refresh: Open a twenty-story page, request older eligible stories and refresh for newer ones.
- Remove ineligible stories: Deleted or newly restricted posts disappear from future authorized responses even if candidate caches still contain their IDs.
- Recover inactive feeds: Reconstruct a returning inactive user's feed instead of treating cache eviction as an empty feed.
- 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
- 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.
- Freshness: Eligible publications become available within five seconds.
- Availability: 99.95% read availability. These are exercise targets, not measured product facts.
- Cold start: Returning inactive readers may wait longer for initial reconstruction under a separately documented target.
- 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.
- Regional recovery: Restore authoritative content, relationships, request identities and change-log positions before claiming current privacy checks.
- 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.
- 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.
The simple design is correct and reconstructable; repeated fan-in is its scaling cost.
Read each connection in order
- syncPublish p882 / request feedPublishing / reading clients → Feed application
- syncCommit post; query followed timelinesFeed application → Posts / follows / timelines
- syncCheck current eligibility; hydrateFeed application → Posts / follows / timelines
- syncReturn ordered stories and cursorFeed application → Publishing / reading clients
- 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.
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.
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.
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.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.
Fanout and pull paths meet before ranking. Current authority gates output even when candidates are stale.
Read each connection in order
- sync1a. Publish k91 / media referencesReader / author clients → Post publication API
- sync2. Commit p882 + outboxPost publication API → Post authority / author timelines
- async3. Relay committed eventPost authority / author timelines → Publication change log
- async4. Consume publicationPublication change log → Batched fanout workers
- syncPage followers and active recipientsBatched fanout workers → Follow / group / block authority
- async5. Idempotent viewer/post upsertBatched fanout workers → Viewer candidate partitions
- asyncNew stories hintBatched fanout workers → New-story notification gateway
- asyncOptional lightweight updateNew-story notification gateway → Reader / author clients
- sync1b. Feed request / cursorReader / author clients → Feed assembly API
- sync6. Ordinary-author candidatesFeed assembly API → Viewer candidate partitions
- sync7. Pull high-fanout timelinesFeed assembly API → Post authority / author timelines
- sync8. Deduplicate and score candidatesFeed assembly API → Staged ranker / feature service
- sync9. Ranked candidate IDsStaged ranker / feature service → Current eligibility / hydration
- syncCurrent eligibility / membershipCurrent eligibility / hydration → Follow / group / block authority
- syncCurrent post versions and bodiesCurrent eligibility / hydration → Post authority / author timelines
- sync10. Freeze / resume bounded orderFeed assembly API → Feed session order store
- sync11. Authorized storiesCurrent eligibility / hydration → Feed assembly API
- sync12. Page and cursorFeed assembly API → Reader / author clients
- 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.
- 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.
- A relay publishes e301 to the durable log. If its reply is lost, it republishes the same event identity; downstream work tolerates duplicates.
- 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.
- 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. - 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.
- 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.
- 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.
- 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.
- 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.
- 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.
- 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.
- 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.
Cache membership remains derived; current serving authorization decides whether a story can be returned.
Read each connection in order
- syncRead the viewer follows the author / v6Fanout worker → Relationship authority
- returnEligible at v6Relationship authority → Fanout worker
- syncCommit the viewer unfollow / v7Relationship authority → Relationship authority
- syncLate upsert p882 using old v6Fanout worker → Candidate cache
- syncRead candidate p882Feed reader → Candidate cache
- syncCurrent eligibility for p882Feed reader → Relationship authority
- returnv7: not eligibleRelationship authority → Feed reader
- 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.
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.
Interviewer follow-up
Which part changes when ranking becomes personalized?
Reveal the follow-up answer
Candidate retrieval remains bounded. Viewer and post features now feed predictions such as useful interaction or hide probability; an explicit scoring policy combines them, followed by diversity rules. I would illustrate a weighted score, evaluate its quality, and carry the frozen order and ranking context through pagination. Creation time alone no longer determines the next page.
What the answer must demonstrate: Start with records and a working query.
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.
Interviewer follow-up
Why avoid copying the full video into each feed?
Reveal the follow-up answer
The video is shared media. Each candidate needs a post/media reference; copying bytes per follower multiplies storage and invalidation work without improving the actual ownership model.
What the answer must demonstrate: Separate materialization from client transport.
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.
Interviewer follow-up
Can the hybrid path return a post twice?
Reveal the follow-up answer
Yes, particularly during a policy transition. Merge by stable post ID, and version the fanout policy or tolerate overlapping candidate production while deduplicating delivery.
What the answer must demonstrate: Explain both cost and transition behavior.
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.
Interviewer follow-up
Does a page snapshot override a new block?
Reveal the follow-up answer
No. Stable pagination should not leak content that current access rules prohibit. I can refill candidates or shorten the page while retaining its ordering context.
What the answer must demonstrate: Cached membership is not permission.
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.
Interviewer follow-up
What would actually lose a publication?
Reveal the follow-up answer
Acknowledging a post without durably linking it to an event or later reconciliation can leave some feeds unaware. That is why I use reliable change capture or an outbox.
What the answer must demonstrate: Identify derived state versus source state.
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.
Interviewer follow-up
What happens when the event queue backs up?
Reveal the follow-up answer
Prioritize active viewers and expensive hot authors, and temporarily expand read-time retrieval if capacity permits. Report and alert on freshness lag rather than silently calling stale feeds real-time.
What the answer must demonstrate: Latency and freshness are separate measurements.
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.
Interviewer follow-up
What evidence would make you reverse the change?
Reveal the follow-up answer
I compare the saved candidate writes with extra read-time timeline fan-in, latency and useful stories consumed. If the active audience reads frequently and the author posts rarely, precomputation may again be cheaper. I use the same versioned overlap procedure when switching back.
What the answer must demonstrate: Explain the transition protocol as well as the steady-state threshold.
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.
Interviewer follow-up
Why are private media URLs relevant?
Reveal the follow-up answer
A permanent public media URL can bypass correct feed authorization. Delivery needs appropriately bounded or continuously checked access semantics matching the privacy promise.
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 lessonDesign 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 lessonDesign 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 lessonFinal 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.