← Back to practice catalog

System design interview guide

Facebook Post Search System Design

TL;DR:

A user searches Facebook posts for "beach wedding 2019" and expects results from public posts, friends, groups, and their own history. The hard part is not only finding matching words. The system must never show a private, blocked, deleted, or moderated post to the wrong viewer, even when the search index is a few seconds behind the source of truth.

Overview diagram for Facebook Post Search System Design

Problem statement

Design search over social posts at Facebook scale. The system should index post text and metadata, search across huge posting lists, rank useful results, and enforce privacy for the viewer on every response. The real interview trap is that a matching post is not automatically a valid result. It must pass visibility, graph, block, delete, and moderation checks before the user sees it.

Introduction

Imagine a user searching for "beach wedding 2019." The words match thousands of posts: public albums, friend-only memories, group posts, reposted links, and old photos whose captions were created by an OCR job. A normal web search engine would mostly ask, "Which documents match these words?" A social search engine has to ask a harder question first: "Which matching documents is this exact viewer allowed to see right now?"

That is why this problem is not "put posts in Elasticsearch." An inverted index is only the retrieval tool. The product is a privacy-aware search path that finds candidates, removes anything the viewer cannot see, ranks the remaining results, and stays fast when common terms or viral events create huge posting lists.

The hardest bugs are not always slow queries. A deleted post appearing in search for two more minutes can become a trust incident. A private group post shown in a snippet can become a policy violation. A block relationship missed by a cache can make the search result feel unsafe even if the post service itself would have denied access.

In the interview, your design should keep four ideas separate: the source-of-truth post store, the asynchronous indexing pipeline, the viewer-aware query path, and the safety path for deletes and takedowns. Strong answers explain where privacy runs, how much candidate work is allowed per query, and why freshness is a promise with different levels for normal posts and safety events.

How to approach

Start by turning "Facebook post search" into one readable request and one readable write. You do not need to design the entire social network. You need enough of the post service, graph service, index pipeline, and search service to explain how a user asks a question and how a new or deleted post changes future answers.

Clarifying questions (say these in the room)

You: "Are we designing search across posts only, or do comments, profiles, pages, and marketplace listings also count?"

Interviewer: "Focus on posts. Include public posts, friends-only posts, group posts, and a viewer's own posts."

That answer tells you the main entity is a post document, but the document can belong to different visibility scopes. You should design fields for author, group id, timestamp, post type, language, and visibility rather than treating every post as a public web page.

You: "How fresh should search be after a user creates, edits, or deletes a post?"

Interviewer: "New posts can appear within seconds or minutes, but deletes and takedowns should disappear as fast as possible."

This answer gives you two freshness lanes. Normal indexing can be near-real-time and asynchronous. Safety removals need a faster deny layer on the query path because waiting for segment compaction is not acceptable.

You: "Do we need personalized ranking, or is text relevance plus recency enough?"

Interviewer: "Use text relevance, recency, and simple social signals. You do not need to design a full ML ranking platform."

That keeps the design focused. You can mention BM25-style text scoring, recency boosts, author affinity, group relevance, and spam scores without spending the whole interview on model training.

You: "Can the query path call the social graph for every hit?"

Interviewer: "It can call privacy or graph services, but it must stay within a normal search latency budget."

That answer is the key trap. You can batch-check a small candidate set, but you cannot ask the graph service about every document in a huge posting list. The index must carry enough visibility metadata to prune early, and the aggregator must cap candidates before expensive checks.

You: "What is out of scope: full media OCR, autocomplete, ads, or cross-region compliance?"

Interviewer: "Mention how media text could be added asynchronously, but leave full OCR and ads ranking out."

This keeps the main path clean. You can show extension points for media enrichment and autocomplete, then spend depth on indexing, privacy, ranking, and deletes.

Minute pacing

MinuteWhat to coverWhy it matters
0-5Clarify post scope, visibility rules, freshness, and latency targetThese choices decide whether privacy is a filter, a graph call, or both
5-10Capacity sketch: documents, writes, queries, hot terms, and shard fanoutSearch looks easy until common terms and ACL checks enter the math
10-18Draw the write path from post commit to index refreshThe post write should not wait for every term list and media enrichment
18-28Draw the read path from query parse to shard fanout, merge, privacy, rank, cursorThis is where latency and privacy meet
28-38Deep dive into privacy-safe retrieval, tombstones, and hot termsThese are the interview push points
38-45Failures, observability, abuse, cost, and final tradeoffsThis shows you can operate the design, not only draw it

In the room (a fuller opening you can actually say): "I will design post search, not full Facebook search. A post write commits to the post service first and then emits an event into an indexing pipeline. The index stores tokens plus metadata such as author, timestamp, group id, and visibility. A search request fans out to document shards, gets a bounded candidate set, filters it using viewer context and a tombstone store, ranks safe results, and returns an opaque cursor. Normal new-post freshness can be near-real-time, but deletes, blocks, and takedowns need a faster safety check so the index never becomes the source of truth for privacy."

Out of scope (and why you park these)

Full web search across Facebook. Profiles, pages, marketplace, events, and comments all have different schemas and permissions. Mention that the same retrieval pattern can extend to them, but keep the interview on post documents.

Training a large ranking model. Ranking features matter, but the interview is testing architecture. Use simple text, recency, social, and spam signals so you can spend time on privacy and latency.

Complete media understanding. OCR, image labels, speech-to-text, and video frames can enrich the index later. They should be asynchronous workers, not part of the synchronous post write.

Ads and sponsored results. Ads change ranking, policy, and billing. Park them unless the interviewer explicitly expands the scope.

Global legal compliance in every country. You should say deletes and data residency exist, but a full legal workflow would turn this into a compliance systems interview.

Capacity estimation

The rough numbers matter because they stop you from designing a single database query or an unbounded graph check. You can keep the math approximate and still use it to choose the architecture.

AxisInterview-scale numberWhat it means for design
CorpusBillions of posts and many more indexed termsUse sharded inverted indexes; do not scan the post table
WritesLarge but lower than reads; bursts from popular users and groupsIndex asynchronously from durable post events and batch writes to shards
SearchesHigh read volume with skew toward trending termsQuery service needs scatter-gather, per-shard timeouts, and candidate caps
FreshnessSeconds to minutes for normal posts; faster for deletes and takedownsSeparate normal index refresh from query-time tombstones
Privacy checksPotentially one check per candidateCarry cheap visibility fields in the index and batch-check only a bounded set
RankingTop results only, not full corpus orderingLet shards return local top-k; merge and re-rank the safe top set

These numbers imply three design rules. First, the index is a derived view, not the source of truth. Second, every query must have a work budget: number of shards, candidates per shard, ACL batch size, and ranking timeout. Third, safety removals cannot wait for the slowest part of index maintenance.

High-level architecture

At a high level, the post write path and the search read path are different systems that meet at the index. The post service owns truth. The indexing pipeline owns a searchable copy. The query service owns the user-visible answer.

[ Post service ] -- outbox event --> [ Event bus ] --> [ Index workers ]
       |                                                   |
       | source of truth                                   v
       v                                             [ Index shards ]
[ Post DB ]                                    inverted lists + fields
       |
       +-- delete / takedown event --> [ Tombstone store ]

[ Client search ]
       |
       v
[ Search API ] --> [ Query planner / aggregator ] --> shard top-k fanout
                         |                                  |
                         |                                  v
                         +--> [ Privacy / Graph batch ] <-- candidates
                         |
                         +--> [ Tombstone check ] --> [ Rank + cursor ]

The post service commits the post, edit, visibility change, delete, or moderation decision first. It emits a durable event after commit so index workers can retry without losing changes. Index workers tokenize text, normalize language, attach fields, and bulk-write documents into index shards. Media enrichment workers can add OCR or caption fields later through update events.

The search service receives a query with a viewer id. It parses terms and filters, picks relevant shards, sends requests in parallel, and asks each shard for local top candidates. The aggregator merges those candidates, removes anything blocked by tombstones, runs a viewer-aware privacy filter, hydrates lightweight ranking features, and returns a page with a cursor.

Who owns what:

  • Post service owns source-of-truth content, author, visibility, delete state, and moderation state.
  • Event bus gives the indexing pipeline a durable replayable stream so workers can retry and reindex.
  • Index workers tokenize, enrich, and bulk-write documents into search shards.
  • Index shards own inverted lists, forward fields for snippets, and local top-k scoring.
  • Search aggregator owns fanout, merge, candidate budgets, timeouts, privacy checks, ranking, and cursors.
  • Privacy and graph service answers whether this viewer can see this author, group, audience, and block relationship.
  • Tombstone store blocks deleted or taken-down doc ids while physical index segments catch up.

In the room: Draw the write path and read path separately. Say that the index helps find candidates, but the post service and privacy systems still decide whether a candidate may be shown.

Core design approaches

The main choices are not vendor choices. They are where you pay for coordination: on writes, on reads, or on privacy checks.

Document-sharded inverted index

With document sharding, each shard owns a range or hash of post ids. A new post goes to one primary shard and its replicas, so writes are simple and local. A search query usually fans out to many shards because matching terms may exist anywhere. Each shard returns its local top candidates, and the aggregator merges them into a global page.

This is often the better default for a social product because posts are constantly created, edited, deleted, and re-permissioned. You accept read fanout and build a strong aggregator.

Term-sharded inverted index

With term sharding, each shard owns some terms, so a query can hit fewer shards for certain searches. The cost moves to writes because one post with many terms can update many shards. Hot terms become hot shards, and multi-term queries require coordination across term owners.

This can work for specialized cases, but it is harder to operate when the write stream is large and documents change visibility.

Privacy metadata inside the index

The index should store fields that let it reject obvious misses early: visibility type, author id, group id, language, post type, created time, and moderation state. For public posts, that may be enough. For friends, groups, custom audiences, and blocks, the system still needs viewer-specific information.

The practical pattern is a two-stage filter. First, use indexed fields and precomputed viewer context to prune cheaply. Then batch-check the remaining candidates against the graph or privacy service. The second stage must be bounded so a common term does not create an unbounded privacy job.

Query-time safety layer

Deletes, takedowns, and some visibility changes should also write to a fast tombstone store keyed by doc id. The query service checks this store before ranking. This does not replace normal index deletion; it protects the user while slower segment deletion and compaction catch up.

Data model

The data model should make it clear which fields are source of truth and which fields are copied into the index for fast retrieval.

Entity or storeImportant fieldsWhy it exists
postspost_id, author_id, body, created_at, updated_at, visibility_type, group_id, audience_id, moderation_state, deleted_atSource of truth for content and current state
post_eventsevent_id, post_id, event_type, version, payload, created_atDurable stream for create, edit, delete, visibility change, and takedown
index_documentdoc_id, terms, author_id, created_at, visibility_type, group_id, language, spam_score, versionSearchable copy used by index shards
forward_indexdoc_id, title or snippet fields, safe preview metadataLets the query path build snippets without fetching the full post for every candidate
viewer_context_cacheviewer_id, friend ids or compressed set, group memberships, block set versionSpeeds privacy checks for the current viewer
tombstonesdoc_id, reason, expires_at, source_versionBlocks deleted or taken-down posts while index segments lag
search_cursorencrypted query plan, last score, last doc id, snapshot or time fenceSupports stable pagination without offset scans

The index document should include enough metadata to filter early, but it should not become the privacy source of truth. If a friendship, group membership, or block changes, the query path must use current viewer context or a clearly bounded cache with a short lifetime.

Detailed design

Walk the system through two journeys: one post entering the index and one viewer searching for it. The details below are written as prose because the ordering matters. If you only list "tokenize, index, query, rank," you hide the part of the design that protects privacy.

When a post is created or edited

A user writes a post and the post service commits it to the post database. That commit is the moment the product has accepted the content. The service then records an outbox event in the same transaction or an equivalent durable event flow so the indexing system can retry later. This avoids a dangerous split where the user sees "posted" but the index event is lost forever.

Index workers consume the event and build a search document. They tokenize the text, normalize the language, remove or downweight stop words, store author and timestamp fields, and attach visibility metadata such as public, friends, group id, or custom audience id. They also keep a document version so a slow worker cannot overwrite a newer visibility change with stale metadata.

The worker bulk-writes to the appropriate index shard. The document becomes searchable after the index refreshes, which may happen every few seconds depending on cost and freshness needs. Background merges later compact small segments into larger ones. That merge is an operational detail, but it explains why normal search freshness is near-real-time rather than perfectly instant.

If media enrichment is in scope, OCR and caption extraction run in a separate pipeline. A post can be searchable by its text first and later gain media-derived fields. That keeps post creation fast and prevents a slow image model from blocking the user's write.

When a post is deleted, hidden, or taken down

A delete or takedown starts in the source-of-truth post service. The service marks the post deleted or moderated, emits a normal index event, and also writes the doc id into a fast tombstone store when the removal is safety-sensitive. The normal index update eventually removes or hides the document in search segments. The tombstone protects the query path immediately.

Visibility changes should also carry a version. If a user changes a post from public to friends-only, the new version must win even if older create events arrive later. A simple rule helps: indexers only apply an event if its version is newer than the version currently stored for that document.

When a viewer searches

The client calls the search API with the query text, filters, limit, and cursor if this is not the first page. The API authenticates the viewer and builds a small viewer context: user id, friend set or compressed membership, groups, block relationships, and any policy flags needed by privacy checks.

The query planner parses the query, applies required filters such as time range or post type, and sends the planned query to index shards in parallel. Each shard uses its inverted lists to find matching documents and returns only its local top candidates, not every match. The local score can include text relevance, recency, language, spam score, and simple metadata.

The aggregator merges shard candidates and keeps a hard cap, such as the top few hundred or thousand before privacy checks. It checks the tombstone store first because there is no reason to rank a deleted post. It then applies cheap visibility filters and batches the remaining candidates through the privacy service. The privacy service should answer in bulk so the query does not become one network round trip per candidate.

Only after that does ranking finalize the page. Ranking can boost recent posts, friends, groups the viewer visits often, and exact phrase matches. It should downrank spam and unsafe content. The response returns safe results plus an opaque cursor that encodes the last score, last doc id, and enough query context to continue without offset pagination.

In the room (recap): "A post commit emits a durable index event. Index workers tokenize and write a derived search document with visibility metadata. A search request fans out to shards, merges a bounded top set, checks tombstones and privacy with viewer context, then ranks only safe candidates. Deletes and takedowns get a fast query-time block because normal segment cleanup can lag."

Architectural dig: privacy-safe retrieval under index lag

This is the part of the interview where a normal search design becomes a social search design. The danger is not just that a query is slow. The danger is that a stale index result looks valid enough to display even though the source of truth would deny it.

Privacy-safe search path: query terms hit index shards, then ACL and tombstone checks remove unsafe candidates before ranking

The tempting but unsafe design

A weak design asks the index for all matching documents, ranks them, and then lets the application hide results that fail privacy. That has two problems. For hot terms, the candidate set may be enormous, so the app spends too much time filtering. For private content, even a snippet or title can leak if it is fetched or logged before the check.

Another weak design stores a single visibility = friends field and assumes that is enough. It is not enough because "friends" depends on the viewer, the author, block relationships, custom audience edits, group membership, and sometimes moderation policy.

The better retrieval shape

Use the index for what it is good at: term lookup and cheap field filtering. Store fields such as public, group id, author id, post type, language, and created time so shards can reject obvious misses early. Then let the aggregator do a bounded second phase using current viewer context.

The viewer context can be compressed. For example, a friend set can be represented as a cached membership structure with a version. Group membership can be checked in bulk. Block sets should be small and fast to apply. The important point is that the query checks the viewer, not only the document.

The tombstone layer

Near-real-time indexing means the old document may still exist in some segment for a short time. A tombstone store gives the query path a faster deny decision. When moderation removes a post, the doc id goes into this store immediately. Search checks the store before snippets and ranking.

Tombstones need retention long enough to cover index cleanup and replay. They also need version information so an old delete does not hide a newly created document if doc ids are ever reused, which they should not be. In most systems, doc ids are immutable and never reused, which makes tombstones much safer.

The scale inside the box

The aggregator should not ask the privacy service about unbounded candidates. A good answer sets a cap: each shard returns local top-k, the aggregator keeps a global candidate pool, and privacy checks run in batches. If the privacy service is slow, the system can return fewer results or public-only degraded results depending on product policy, but it must not skip privacy and show forbidden content.

The interview land

Land on this sentence: "The index finds candidates; privacy and tombstones decide whether a candidate can become a result." That sentence tells the interviewer you understand the boundary between search relevance and social trust.

Key challenges

Privacy is viewer-specific. A post can be visible to one user and forbidden to another. The index can store document metadata, but it cannot replace the current viewer relationship graph.

Freshness has different meanings. A new post appearing after thirty seconds may be acceptable. A deleted private post appearing after thirty seconds may not be acceptable. Treat safety freshness separately.

Hot terms create huge posting lists. Words like "birthday," celebrity names, or viral hashtags can match millions of documents. Candidate caps and max-score pruning matter more than adding another application server.

Ranking must happen after safety gates. If ranking fetches snippets or features for private posts before filtering, logs and caches can leak information even if the final response hides the post.

Pagination is hard when rank changes. Offset pagination repeats or skips results when scores change. Use opaque cursors with stable score and doc id boundaries, and be honest that a very fresh index can still move items between pages.

Scaling the system

Scale the index horizontally by sharding documents and adding replicas for read throughput. Replicas help query volume, while more primary shards help write and storage distribution. Too many shards increase fanout cost, so the aggregator must know how many shards it can touch within the latency budget.

Use bulk indexing workers rather than one network call per post term. Batch by shard, use backpressure from the index cluster, and keep replay from the event bus so workers can recover after failures. A reindex job should read from the source of truth or durable event log, not from whatever happens to be in the current index.

For hot queries, cache carefully. Public head queries can use short-lived shared cache. Personalized queries need viewer-aware cache keys, which lowers hit rate and raises privacy risk. You can cache pieces, such as parsed query plans or public shard top-k, but be cautious about caching final personalized results.

For celebrity authors or large groups, expect write and read skew. A single celebrity post can produce many searches and many privacy checks. Dedicated shards, routing rules, or separate safety queues for high-risk entities can reduce blast radius.

For multi-region systems, either keep regional indexes and federate queries, or route users to a primary region for consistent privacy. Exact global freshness is expensive. It is better to state a policy, such as local-region search with cross-region backfill, than to pretend one index is magically fresh everywhere.

Failure handling

FailureWhat users or teams seeWhat to build
One index shard is slowSearch p99 rises or results look thinPer-shard timeout, partial results policy, and shard-level latency alerts
Privacy service is slowSearch hangs on logged-in queriesBatch checks, strict timeout, and fail closed for private candidates
Index workers lagNew posts missing or old versions still searchableIndex lag metrics, replayable events, and separate safety tombstones
Tombstone store failsDeleted content may leak if not guardedTreat as safety-critical; fail closed or return reduced public-only results
Ranking service times outResults are less personalizedFall back to text score plus recency after privacy checks
Event bus replay duplicates eventsIndex version conflicts or stale writesIdempotent index updates using post_id and monotonic version

The rule is simple: degraded search may return fewer results, slower results, or less personalized results. It must not return private or deleted results.

API design

The public API should make query cost visible through required bounds and cursor pagination.

EndpointRole
GET /v1/search/postsSearch visible posts for the authenticated viewer
POST /internal/index/posts:reindexInternal admin trigger for backfill or repair jobs
POST /internal/index/posts:tombstoneInternal safety path to block a doc id quickly

GET /v1/search/posts query params:

ParamRole
qQuery text
limitPage size with a server cap
cursorOpaque pagination token
author_idOptional author filter
group_idOptional group filter, still checked against viewer membership
since, untilOptional time window that helps bound work
typeText, link, photo, or video

Hot read path:

GET /v1/search/posts?q=beach&wedding
        |
        v
[ Search API ] -> viewer context -> query planner
        |
        v
[ Index shards ] -> local top-k -> aggregator
        |
        v
tombstone check -> privacy batch -> rank -> cursor response

Response shape:

{
  "results": [
    {
      "post_id": "p_123",
      "author_id": "u_42",
      "snippet": "Beach wedding photos from 2019...",
      "created_at": "2019-07-20T10:00:00Z",
      "reason": "friend_post"
    }
  ],
  "next_cursor": "opaque-token"
}

Errors: Return 400 for malformed query, 401 for unauthenticated access if the product requires login, 429 for query abuse, and 503 when the system chooses not to serve partial results. Do not reveal that a private post exists through an error message.

Observability

Observe the write path, read path, and safety path separately. A single "search is up" dashboard can be green while delete lag is unsafe.

Track index lag from post commit time to searchable time, broken down by create, edit, visibility change, delete, and takedown. Track tombstone propagation time separately because it is the safety lane. Track event replay lag and index worker failure rate so you know whether search is falling behind the post service.

On the query side, track p50, p95, and p99 by phase: parse, shard fanout, shard wait, merge, tombstone check, ACL batch, ranking, and hydration. Also track per-shard latency, timeout rate, candidate count before privacy, candidate count after privacy, and empty result rate.

For trust, track privacy-filter drop rate, blocked-result attempts, deleted-doc hits caught by tombstones, and snippet-generation failures. These metrics tell you when the index is returning unsafe candidates that the later guards are saving.

For abuse and cost, track top queries by CPU, top users by query rate, hot term posting list sizes, cache hit rate, and expensive query rejects.

Security and abuse

Privacy is the main security requirement. Every search request must run under an authenticated viewer context unless the product explicitly supports public search. The query path should never trust client-supplied viewer ids or group ids without server-side auth.

Avoid leaking existence through snippets, logs, caches, and errors. If a private post is filtered out, the response should look like the post never existed. Query logs should not store raw private snippets or full viewer context. Internal debugging tools need role-based access and audit trails.

Protect the system from query abuse. Rate limit by user, IP, device, and app. Reject or rewrite pathological queries that expand into huge work, such as leading wildcards or very large OR expressions. Apply spam and safety scores during indexing so abusive posts are not rewarded by search.

Treat privacy caches carefully. Short TTLs and version stamps help, but a stale friend list can still leak. For sensitive cases such as blocks, takedowns, or private groups, prefer fail-closed behavior over showing a result from stale cache.

Cost awareness

Search costs money in three places: storing index replicas, refreshing and merging segments, and running query fanout plus ranking. A design that indexes every field and refreshes every second may be fresh, but it can make the cluster too expensive to operate.

Choose fields deliberately. Index terms needed for search and filters. Store some fields only for display or snippet generation. Avoid indexing high-cardinality or low-value fields that users rarely filter on. Media enrichment should be opt-in per field because OCR text, labels, and embeddings can multiply storage.

Use lifecycle policies. Recent posts and hot segments live on fast storage. Older documents can move to cheaper tiers or lower-replica indexes if the product accepts slower search for old content. Tombstones can expire after index cleanup is proven complete, but audit records should remain according to policy.

Cache only where it is safe. Shared public query caches save money. Personalized result caches can leak or become stale if viewer permissions change, so keep their TTL short and include viewer context in the key.

Production scenes

The moment is tense because the moderation team has already removed a harmful post from the main feed. Support confirms the feed is clean. Then someone searches the exact phrase and sees the old snippet. The dashboard says indexing lag is only forty seconds, but forty seconds is a long time when the result is a policy violation.

The trap is treating normal near-real-time freshness as enough for safety. Segment deletion and merge are not instant. A search index is a derived copy, and it may continue returning old doc ids until the delete event is applied everywhere.

What to build is the tombstone lane. Moderation writes the doc id into a fast deny store immediately. Search checks this store before snippets and ranking. Index cleanup still happens, but the user-visible guard is independent from the slowest segment.

Scene 2: A viral hashtag makes p99 explode

Everyone searches the same hashtag after a live event. Median latency looks fine because most queries are normal, but p99 becomes seconds for the hot term. The aggregator pulls too many candidates from too many shards, then privacy checks become the new bottleneck.

The trap is assuming inverted indexes make every term cheap. Common terms create huge posting lists, and social search adds ACL work after retrieval. More application servers do not fix a single overloaded shard or a privacy service doing unbounded batch work.

What to build is candidate budgeting. Each shard returns a small local top-k with early termination when possible. The aggregator caps global candidates, coalesces identical public queries, and applies strict timeouts. For trending queries, cache public-safe heads with short TTL and keep personalized reranking bounded.

Scene 3: A block relationship changes while cached viewer context is stale

A user blocks another account and immediately searches old posts. If the search service uses a cached friend or block set for too long, it may show a result the user expected to disappear. The post service may enforce the block correctly, but search feels broken because it used older context.

The trap is treating viewer context as static. Social relationships are data too, and privacy depends on their current version. A long-lived cache can turn a fast search path into a trust bug.

What to build is versioned viewer context with short TTL and stronger handling for block events. Blocks and safety actions can invalidate or bypass cache. If the privacy service cannot confirm the relationship, the search path should hide uncertain private candidates rather than show them.

Bottlenecks and tradeoffs

Privacy exactness vs query latency

The tension is that exact privacy checks may require current graph state, group membership, block lists, and custom audiences. Those checks add network calls and can dominate latency.

What breaks is either privacy or p99. If you cache too aggressively, you risk showing forbidden posts. If you check every candidate synchronously, hot queries time out.

Teams usually land on indexed metadata plus bounded batch checks. Use cheap filters first, then ask the privacy service only about a capped candidate set. Say the cap out loud.

Freshness vs indexing cost

The tension is that more frequent index refresh makes new posts searchable faster but increases CPU, I/O, and merge pressure. For normal posts, seconds or minutes may be fine. For takedowns, it is not fine.

The practical answer is two lanes: normal near-real-time indexing for creates and edits, and a faster deny path for deletes, blocks, and moderation. That gives users acceptable freshness without making every refresh safety-critical.

Personalization vs cache hit rate

Public search results cache well. Viewer-specific search results do not. The more ranking depends on friends, groups, and blocks, the less useful a shared cache becomes.

Use caching for public pieces such as query parsing, shard-level heads for public documents, and short-lived popular query plans. Be careful with final personalized result pages because permissions can change.

Document sharding vs term sharding

Document sharding keeps writes local but makes reads fan out. Term sharding can reduce some read fanout but makes writes touch many places and creates hot term owners.

For a high-write social product, document sharding is usually the cleaner default. Then invest in the aggregator, candidate caps, and per-shard budgets.

Interview tips

Do not make ACL a footnote

You might say: "We retrieve matches from the index and then filter by privacy."

Interview Push: "A common term returns ten million candidates. Are you going to graph-check all of them? Also, what if a private snippet is fetched before filtering?"

Land here: Explain a two-phase retrieval plan. The index stores visibility fields that prune obvious misses. The aggregator keeps a bounded candidate set and calls the privacy service in batches. Snippet generation and ranking happen only after tombstone and ACL checks. This shows that privacy shapes the search architecture, not only the response formatter.

Treat deletes differently from normal freshness

You might say: "The index is eventually consistent, so deletes disappear after the next refresh."

Interview Push: "A moderated post is still visible in search for one minute. Is that acceptable?"

Land here: Separate normal post freshness from safety freshness. New posts can be near-real-time, but deletes and takedowns write to a query-time tombstone store immediately. Index cleanup still runs in the background. The user-visible rule is that removed doc ids are blocked before ranking.

Pick a sharding story and name its cost

You might say: "We shard the index."

Interview Push: "By what, and who pays when a post has hundreds of terms or a query touches every shard?"

Land here: Choose document sharding for write locality, then own the query fanout. Each shard returns local top-k under a timeout; the aggregator merges and caps candidates. If you discuss term sharding, say that writes now touch many term owners and hot terms become hot shards.

Rank only safe candidates

You might say: "We rank results with text score, recency, and social signals."

Interview Push: "Do you rank before or after privacy? Could a private post be logged or cached during feature hydration?"

Land here: Rank after safety gates for the final page. Shards can compute rough local text scores, but the aggregator should run tombstone and ACL checks before snippet hydration and personalized reranking. Logs and caches should not contain forbidden snippets.

Close with one production failure

You might say: "We monitor search latency and errors."

Interview Push: "What incident would this system actually have?"

Land here: Pick one concrete scene: stale takedown, hot hashtag p99, or stale block cache. Name the metric and fix. For example, "deleted-doc hits caught by tombstones" proves the safety layer is doing real work.

What should stick

  1. The index finds candidates; privacy creates results. A matching post is not safe to show until viewer permissions, blocks, group membership, and tombstones pass.
  2. Indexing is asynchronous, but safety has a faster lane. Normal post freshness can lag. Deletes and takedowns need query-time protection.
  3. Bounded candidates protect latency. Never graph-check an unbounded posting list for a hot term.
  4. Shard choice moves cost. Document sharding simplifies writes and makes reads fan out; term sharding does the opposite.
  5. Ranking comes after safety. Snippets, caches, and logs can leak if they touch private posts too early.

Tell it in the room: "Post writes commit to the post service and emit index events. Index workers tokenize and store searchable documents with visibility metadata. Search fans out to shards, merges a bounded candidate set, checks tombstones and privacy in batches, then ranks safe results with a cursor. Hot terms need candidate caps, and deletes need a fast deny layer because the index is a derived copy."

Key Takeaways

  • Use an inverted index, but do not confuse term matching with permission to display a post.
  • Store enough metadata in the index to filter cheaply, then batch-check viewer-specific privacy on a bounded set.
  • Treat deletes, takedowns, and blocks as safety events with a query-time tombstone or deny layer.
  • Design the aggregator as a first-class component because scatter-gather, merge, timeouts, ACL, and ranking meet there.
  • Measure privacy drops, tombstone hits, ACL latency, index lag, and per-shard p99, not only total search QPS.

Quick revision notes

Use this as a last look before a mock interview, not as a substitute for the full walkthrough above.

Clarify the surface. Confirm posts only, not all Facebook entities. Ask about privacy scopes, freshness, deletes, ranking, and media enrichment. This prevents you from drawing a generic web search engine.

Draw two paths. Post writes commit to the post service and flow asynchronously into index shards. Searches fan out to shards, merge candidates, check tombstones and privacy, rank, and return a cursor.

Say privacy early. The viewer id is part of the query plan. Indexed visibility fields prune obvious misses, but viewer-specific graph and block checks still run on a bounded candidate set.

Separate normal freshness from safety freshness. New posts can appear after a short delay. Deletes and takedowns need a fast deny store because segment cleanup can lag.

Operate the real risks. Watch index lag, delete lag, ACL batch latency, per-shard p99, privacy-filter drop rate, and hot query cost.

Practice this path: Facebook Post Search System Design.

What interviewers expect

  • Explain the indexing pipeline from post events to tokenizer, forward index, inverted index shards, refresh, and background segment merge.
  • Use an inverted index for term lookup, but make privacy part of retrieval with visibility metadata, audience keys, block sets, and a bounded ACL batch check.
  • Separate normal near-real-time freshness from safety-critical deletes and takedowns. A fast tombstone layer should block doc ids even if index segments lag.
  • Shard the index deliberately, usually by document id for write locality, and accept query fanout plus a merge service with strict per-shard budgets.
  • Rank only candidates the viewer may see. Mention recency, author affinity, group membership, text score, spam score, and stable cursor pagination.
  • Operate the system with metrics for index lag, delete lag, ACL latency, shard p99, empty result rate, and privacy-filter drop rate.

Interview workflow (template)

  1. Clarify requirements. Confirm functional scope, users, consistency needs, and which non-functional goals matter most (latency, availability, cost).
  2. Rough capacity. Estimate QPS, storage, and bandwidth so your data model and partitioning story are grounded.
  3. APIs and core flows. Define a minimal API and walk 1–2 critical read/write paths end to end.
  4. Data model and storage. Choose stores for each access pattern; call out hot keys, indexes, and retention.
  5. Scale and failure. Add caching, sharding, replication, queues, or fan-out as needed; say what breaks in failure modes.
  6. Tradeoffs. Name alternatives you rejected and why (e.g. strong vs eventual consistency, sync vs async).

Frequently asked follow-ups

  • How do you enforce privacy during search?
  • How does a new post become searchable?
  • How do deletes and takedowns leave the index?
  • How do you shard an inverted index?
  • How do you keep query latency low for hot terms?

Deep-dive questions and strong answer outlines

How do you enforce privacy without graph-checking millions of hits?

Store cheap visibility metadata in the index, such as public, author-only, friends, group id, or custom audience id. Use those fields to prune obvious misses inside the search engine, then batch-check a bounded candidate set against the graph and privacy service. The important phrase is bounded candidate set: never fetch ten million matching doc ids and call the graph service one by one.

How does a post enter the index?

The post service commits the post as source of truth and emits an outbox event. Index workers tokenize text, normalize language, attach metadata such as author, timestamp, visibility, and group id, and bulk-write to index shards. The post becomes searchable after the next refresh. Background merges compact segments later, so freshness is near-real-time, not instant truth.

What happens when a post is deleted or taken down?

The source-of-truth post service emits a delete or takedown event. Indexers remove or mark the document in normal segments, but the query path also checks a fast tombstone store for safety-critical doc ids. That means a removed post can be blocked immediately even when the physical index segment is not compacted yet.

How do you shard the index?

A common choice is document-based shards: each shard owns a range or hash of doc ids, writes stay local, and every query fans out to the relevant shards. Term-based shards reduce query fanout for some searches but make every new post touch many shards. In a social product with heavy writes and many updates, document sharding plus a fast aggregator is usually easier to operate.

Where does ranking happen?

Shards return local top candidates using text score and simple fields. The aggregator merges them, applies privacy and tombstone checks, hydrates lightweight ranking features, and re-ranks the safe set. Ranking should not promote a post before the viewer is allowed to see it.

AI feedback on your design

After a practice session, InterviewCrafted summarizes strengths, gaps, and interviewer-style expectations—similar to a written debrief. See a static example report, then practice this problem to get feedback on your own answer.

FAQs

Q: Is Elasticsearch or OpenSearch enough?

A: It can be the storage and query engine, but it is not the whole design. The interview is about the pipeline around the index: events, privacy metadata, ACL checks, delete safety, shard fanout, ranking, and operational limits.

Q: Should search results be perfectly fresh?

A: Normal post discovery can be near-real-time, often seconds to minutes. Safety deletes, account blocks, and takedowns need a faster guard, usually a tombstone or deny-list check on the query path, because stale private content is a trust incident.

Q: Do we build a per-user index?

A: A full per-user index is usually too expensive for a large social graph. Use shared document shards with visibility metadata and viewer-aware filters. Special private surfaces may add per-user or per-group caches, but they do not replace the global index.

Q: How do photos and OCR text fit?

A: Media understanding is an enrichment pipeline. The original post can be indexed first, then OCR, alt text, object labels, or captions add searchable fields later. Do not block post creation on slow media analysis.

Practice interactively

Open the practice session to use the canvas and stages, then review AI feedback.