System-design interview · Core interviews
Design public post search
Build a recoverable public-post search index, then merge bounded shard results without confusing matching, ranking, freshness and permission.
You will learn to
- Trace terms into an inverted index and distinguish Boolean matching from ordering.
- Explain time/document partitioning and stable bounded pagination.
- Handle repeated edits/deletes, index recovery and current-content checks safely.
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 · Data partitioning and sharding · Replication and durability · Message queues, event logs, delivery guarantees, and backpressure
Workload and timing examples are interview assumptions.
Dotted concept links open the relevant explanation in a new tab.
01Choose public keyword search and its limits
Design keyword search over public short posts. A query can combine terms with explicit AND or OR and request newest-first results. Add lexical relevance as a discussed extension; most-liked and personalized ordering require their own feature and candidate-selection contracts. The source posting service already exists and remains responsible for accepting posts and determining current visibility.
Use three running documents:
| Document | Text |
|---|---|
T101 |
solar battery |
T102 |
solar roof |
T103 (newer) |
new solar battery |
The query solar AND battery should match T101 and T103, not T102. This small example lets the interviewer verify the search semantics before discussing a cluster.
Accept several seconds between source commit and search visibility. An accepted source post need not appear in search immediately. Deletion and restriction are checked when results are returned, so an old index entry cannot grant access. Choose the recent two years as the interactive search scope and a slower path for older retained history.
Bound query length, page size and historical span. For this design, a missing required shard causes an explicit retryable search failure. Partial results could be a product option, but the API must then disclose incomplete coverage; a short list cannot silently claim to be exhaustive.
Clarify Boolean matching and sort order, how long indexing may lag, the interactive history window, and whether partial results are acceptable. The chosen requirements use newest-first results and an explicit failure when a required shard is missing.
02Functional requirements
Agree on these supported actions before selecting components.
Search public posts. Accept explicit AND/OR keyword queries and return matching current public posts in newest-first creation-time/ID order.
Page through bounded results. Let readers continue a query through a consistent ordered set of results for a limited browsing period; reject expired or mismatched continuation requests.
Apply source changes. Index creates, edits and deletions from the existing posting service, using source-assigned versions and compatible analyzers.
Disclose query failure. Return an explicit retryable error if a required shard cannot answer. Do not silently present incomplete shard coverage as exhaustive results.
03Non-functional requirements
Use these as illustrative interview assumptions to agree with the interviewer. Numerical targets require measurement; they are not claims about an existing product or a proven implementation. p95 (the 95th percentile) means 95% of measured operations finish within the stated time.
Workload and history. Use approximately 28,935 searches/s and 23,150 source changes/s at the planning peak. Interactive search covers two years; older retained history has a separate slower path.
Query latency. Target regional p95 response latency of 300 ms for the agreed bounded query mix at admitted peak load during normal operation. The benchmark must include common terms, shard merge and final source checks, not only cached rare-term queries.
Index freshness. Target p95 source-commit-to-searchable delay within five seconds during normal ingestion. Newly matching edits can be absent until indexed even when every returned record is current.
Result consistency. Use one pinned index snapshot for a page sequence, with a two-minute lifetime and at most 100 results/page. Recheck current deletion, visibility and matching text; the snapshot does not freeze permission.
Recoverability. Retain durable source snapshot/change-log coverage sufficient to rebuild a lost search shard. A search-process or index-node failure must not destroy the source; if required history is unavailable, stop and rebuild from a newer source snapshot.
Bounded and authorized work. Limit terms, historical span, candidate/refill work and page contexts. Return an error or shorter authorized page rather than unbounded work or uncertain private content.
04Retrieve matching IDs instead of scanning every body
An inverted index maps a term to the document IDs containing it. Each such list is a posting list.
| Term | Posting list in the running example |
|---|---|
solar |
T101, T102, T103 |
battery |
T101, T103 |
Intersect the lists for AND, or union them and remove duplicates for OR. Then sort the matching IDs by creation time and ID, newest first.
One search process with a durable local search index is enough for the first version. The source stores a post and a pending change event in one transaction. A background indexer consumes that event, splits text into searchable terms and normalizes them using analyzer A1. An analyzer is the configured text-processing pipeline, and queries must use compatible rules.
After the index refreshes, the query can see T103. The search process retrieves its current source body and visibility, confirms it is still an eligible match and returns it. This final record-loading step is often called hydration. It prevents a candidate ID from being mistaken for permission to return stale cached text.
Use a mature search engine for the index files and posting traversal. The interview’s task is to explain the data path, updates and guarantees, not implement compression formats. This baseline is complete, but one process eventually runs out of useful storage, query CPU or ingest capacity.
The API reads the index for candidates and checks source records before returning text.
Read each connection in order
- syncBoolean querySearch client → Search API
- syncFind candidate IDsSearch API → Inverted index
- syncLoad current visible bodiesSearch API → Source posts + changes
- asyncVersioned changesSource posts + changes → Indexer
- asyncApply then refreshIndexer → Inverted index
05Measure posting work and distributed fanout
Assume 400 million posts/day averaging 300 bytes and 500 million searches/day. Use fifteen indexed terms per post as a storage estimate and fivefold peak traffic.
| Quantity | Calculation | Consequence |
|---|---|---|
| Source writes | 400 million / 86,400 ≈ 4,630/s | About 23,150/s at peak. |
| Searches | 500 million / 86,400 ≈ 5,787/s | About 28,935/s at peak. |
| Five-year source text | 120 GB/day × 365 × 5 | 219 TB before indexes and replicas. |
| Two-year posting payload | 292 billion posts × 15 terms × 5 B | 21.9 TB before positions, versions and overhead. |
| Forty-shard peak fanout | 28,935 × 40 | About 1.16 million shard queries/s. |
If each shard returns 100 candidates of 32 bytes, forty shards send 128 KB per query, approximately 3.7 GB/s at peak just for merging. By contrast, twenty returned 300-byte bodies total only 6 KB of raw text. Small result pages do not imply small retrieval work.
The five-byte document reference is an illustrative packed representation with enough range for this population, not a recommended permanent allocation format. Vocabulary strings are much smaller than all their postings. Measure common-word posting lengths, compression, deletion overhead and rebuild capacity before concluding the index fits in memory.
06Make query meaning and continuation explicit
Search request
GET /search?q=solar%20AND%20battery&sort=latest&limit=20
| Response information | Meaning |
|---|---|
| Results | Current eligible matching posts in the requested order. |
| Opaque continuation cursor | Identifies where a later page resumes. |
| Search snapshot expiry | Tells the client when that page sequence expires. |
Reject malformed Boolean syntax instead of silently changing AND into OR. Authentication supplies quota and visibility scope.
A point-in-time snapshot retains a chosen visible index state for a bounded page sequence. The cursor binds that snapshot, normalized query, filters, sort definition and last returned sort values. Search-after pagination resumes beyond that last tuple. New posts therefore do not shift the numeric offsets of later pages.
Choose a two-minute snapshot lifetime and a maximum of 100 results per page for this exercise. Expired cursors require a restart. Reusing a cursor with another query is an error. Current deletion and access checks still run on each page: snapshot stability does not freeze permission.
The ingestion interface distinguishes source updates from search requests.
| Event information | Purpose |
|---|---|
| Post ID | Identifies the document being changed. |
| Source-assigned version | Lets indexing reject an obsolete update. |
| Operation | Distinguishes create, edit and delete. |
| Durable source-log position | Identifies progress through recoverable source history. |
Source commit success and searchable visibility are separate API facts; report index freshness operationally rather than claiming that storing an event instantly updates every query replica.
07Preserve document identity across edits
Separate authoritative post records from the search index built from them.
| Stored structure | Information | Purpose |
|---|---|---|
| Source posts | Stable post ID, current version and visibility | Supplies the current post and access state. |
| Source change log | Recoverable post updates | Supplies the changes needed to rebuild the index. |
| Derived search index | Term dictionaries, postings and each document's latest applied version or delete marker | Finds candidate documents while tracking which source version was indexed. |
Optional term positions support phrase search, but add bytes; the basic AND/OR scope does not require that feature.
Choose creation-time buckets, subdivided by a hash of post ID, for index ownership. All terms of one post live in the same document shard, and an edit stays in the original bucket. Moving a post whenever it is edited would complicate removal from its old location and duplicate suppression.
A body cache uses post ID and immutable content version. Query caches store candidate identities under their query, filters, sort and index version; cached candidates still undergo current checks. A post’s body and the permission decision must refer to the same version. Do not authorize public version one and then accidentally substitute an edited private version two.
Keep engagement features separate from lexical postings so every new like need not rewrite the text index. In the selected newest-first design, those features can be delayed display fields. A future exact most-liked sort must use them during candidate selection, not only rerank twenty recent matches.
08Partition by documents and prune by time
Document partitioning keeps the words and version of each post together. Each shard can evaluate the complete Boolean query locally, then return its newest candidates. The coordinator merges shard results by the same creation-time/ID order. Time buckets allow a last-day query to skip older indexes, while a broad historical query deliberately pays a larger fanout cost.
Term partitioning is a different option: solar might live on one machine and battery on another. It makes some single-term queries local, but common terms become hot and multi-term intersections cross owners. For the worked workload, choose time/document partitioning and accept bounded scatter/gather—the coordinator sends a query to relevant shards and gathers their answers.
Add replicas when query CPU saturates. They distribute independent queries but also consume index storage and ingest traffic. Select replicas that can serve the pinned snapshot. A replica being alive does not establish that it contains the required version.
Allocate a deadline for shard work that leaves time for merging and final record/visibility checks. Limit terms, candidates, refill rounds and page contexts. Under the selected strict-coverage policy, a required shard timeout fails the query; do not retry every shard repeatedly and multiply the overload.
Versioned source changes feed rebuildable index shards. The coordinator queries every required time/document shard at the chosen snapshot, merges newest-first candidates, then checks current matching text and visibility. Cached candidates take the same final checks; a required-shard timeout fails the strict-coverage query.
Read each connection in order
- syncBoolean query + time rangeSearch client → Search coordinator
- syncRequired shards; pinned snapshotSearch coordinator → Time/document shard replicas
- syncCandidate / immutable-body lookupSearch coordinator → Candidate + versioned body caches
- syncCurrent text and visibility checkSearch coordinator → Current posts + change log
- asyncReplayable versioned changesCurrent posts + change log → Version-aware indexers
- asyncApply version; refresh search viewVersion-aware indexers → Time/document shard replicas
09Apply newer document versions monotonically
Suppose T103 is edited at version 11 and deleted at version 12. If an indexer blindly applies a delayed version-11 event after the delete, it recreates a searchable old document. The writer must atomically compare the incoming version with the stored version and apply only a newer one.
A delete retains its version marker while older events can still replay. Simply removing the document and forgetting version 12 leaves no evidence that version 11 is obsolete. Repeated version-12 delivery has no additional effect. The index engine must prevent another update from intervening between the version check and the write, using serialized updates or an atomic version condition. A separate unlocked read is insufficient.
Persist source progress only after the corresponding index changes are recoverable. If an indexer applies an update and crashes before checkpointing, replaying it is safe through the version rule. If it checkpoints first and crashes before durable indexing, recovery may skip an update that never survived.
A source snapshot plus its matching change-log position allows a new index generation to be built, then caught up with subsequent events. Validate that generation before routing new searches to it. The precise multi-partition snapshot/watermark protocol is an Advanced follow-up, but the basic design must identify its recovery source and never claim a rebuild is current after incomplete replay.
The per-document version guard rejects older work after the delete is recorded.
Read each connection in order
- syncDelete T103 version 12Change log → Index writer
- syncAtomically save delete / version 12Index writer → Document version
- syncDelayed edit version 11Change log → Index writer
- blocked11 is older: do not replaceIndex writer → Document version
10Return current matching content, then explain ranking
For solar AND battery, shards return matching candidate IDs and their sort values. The coordinator merges them, removes duplicates and batch-loads source records with current visibility. If T103 is now private or deleted, omit it. If it was edited to “new solar roof,” its current body no longer satisfies the query; re-evaluate the Boolean terms against that returned version and omit it until the index catches up.
This final check prevents incorrect disclosure and stale-text matches, but it cannot discover every newly matching edited post before indexing. Some newly matching posts and their correct ranking remain missing until indexing catches up. Fetch a bounded number of extra candidates after filtering; return a shorter page rather than loop indefinitely.
Matching and ranking are separate. Both T101 and T103 match the AND query, but newest-first favors T103. A lexical relevance score such as BM25 considers term frequency, corpus rarity and document length; it may favor the shorter, more focused T101. Scores from different shards need compatible analyzers and a defined corpus-statistics policy before they can be compared meaningfully.
Do not promise globally most-liked results by reranking only the newest twenty. A highly liked older match may never enter that candidate set. Either include popularity in each shard’s candidate selection using a pinned feature version or describe the result as a bounded-candidate approximation.
11Recover the index and bound expensive queries
The durable source and its retained change log recover lost search indexes. Restore a validated snapshot and replay from its recorded position. If the log no longer covers the gap, take a newer source snapshot instead of pretending a partial replay is complete. Keep capacity for temporary old/new index generations during analyzer changes and rebuilds.
Measure source-commit-to-searchable delay, query tail latency, posting entries scanned, shard fanout, timeout rate and rebuild duration. Check deletion behavior separately from ordinary indexing freshness. A fast response missing half the intended shard coverage is not a successful search under the chosen contract.
Rate-limit by authenticated client and query cost. Bound wildcards or omit them, cap terms and historical span, and cancel work after deadlines. Popular query caches can save repeated traversal, but arbitrary new queries still need real work. Current visibility failure causes omission or query failure, never optimistic disclosure.
Roll out analyzer changes into a separate generation and compare fixed examples for AND/OR behavior, languages, relevance and deletion. Removing stop words or changing normalization alters meaning; it is not merely a performance optimization. Search replicas and caches improve serving capacity, while durable source history and tested replay provide recovery.
12Check the design against the requirements
Use the agreed lists to check the finished design. The tests below still need to establish the targets; a proposed mechanism is not a measured result. FR refers to the numbered functional requirements above; NFR refers to the numbered non-functional requirements.
| Requirement | Design mechanism | Validation and remaining limit |
|---|---|---|
| FR1 + NFR2,4: correct fast query | Compatible postings, time/document shards, common sort tuples and current source checks. | Test AND/OR against the three documents, then benchmark common-term p95 including all required shards and final checks. |
| FR2 + NFR4: stable continuation | Bounded snapshot and query-bound search-after cursor. | Add new posts between pages, expire the two-minute cursor and delete a prior match. Stable ordering cannot override deletion. |
| FR3 + NFR3,5: fresh recoverable index | Versioned source events, guarded index updates and progress after recoverable writes. | Replay an old edit after deletion and rebuild a shard. Measure five-second freshness separately from source acceptance. |
| FR4 + NFR6: honest degradation | Required-shard deadlines and bounded candidates/refill. | Fail a required shard and expect an explicit query failure; filtered results may be shorter and newly matching edits may still be missing. |
13Rapid revision
Remember: An index finds possible matches; retained versions stop old edits undoing deletes, and current source checks govern returned text.
| Decision | Purpose | Tradeoff or boundary |
|---|---|---|
| Inverted postings | Retrieve matching IDs without a source-table scan. | Common terms still have large lists. |
| Analyze text and queries consistently | Produce compatible index and query terms. | Analyzer changes require a versioned rebuild. |
| Partition by time bucket and document ID | Keep each post’s terms together; skip irrelevant time buckets. | Broad queries contact many shards. |
| Versioned updates and tombstones | Stop repeated or delayed events resurrecting old content. | Keep versions while older events can still arrive. |
| Snapshot plus last returned sort position | Continue through one fixed, expiring index view. | Saved views consume resources and expire. |
| Recheck source version and current visibility | Exclude inaccessible posts and nonmatching edited text. | Extra reads and possibly shorter pages. |
| Newest-first chosen ordering | Merge shard results by creation time, then post ID. | Relevance and popularity need shared scoring rules and inputs. |
Close by tracing T103 through durable source creation, asynchronous indexing, Boolean retrieval, shard merge and current-result checking. Then demonstrate delete version 12 defeating a late edit. The central tradeoff is efficient distributed search with a few seconds of index delay, while source records remain the basis for recovery and permissions. The next measurements are common-term traversal cost, fanout tail latency and rebuild duration.
Practise the interview questions
Say your answer aloud before opening the model answer. Then answer the follow-up and compare the reasoning.
How does solar AND battery find its matches?
Reveal a model answer
Intersect the solar and battery posting lists, yielding T101 and T103 in the example. OR would union IDs and deduplicate.
Interviewer follow-up
Does ranking change Boolean matching?
Reveal the follow-up answer
No. It orders eligible matches after the query’s matching semantics are applied.
What the answer must demonstrate: Demonstrates intersection/union before ordering.
Why can a saved post be missing from search?
Reveal a model answer
Source commit and index refresh are separate stages. A durable change event allows recovery while search visibility is temporarily delayed.
Interviewer follow-up
What does a queue message alone fail to prove?
Reveal the follow-up answer
That the corresponding index change is durable and visible to queries.
What the answer must demonstrate: Separates committed source, recoverable event and searchable refresh.
Why choose time/document shards?
Reveal a model answer
Each post’s terms update together, each shard evaluates the full Boolean query, and time filters skip old buckets.
Interviewer follow-up
What cost remains?
Reveal the follow-up answer
Queries covering many buckets scatter to many shards and wait for their required results.
What the answer must demonstrate: Connects document locality and time pruning to fanout cost.
How does a delete defeat a late edit?
Reveal a model answer
Store the source-assigned latest version and atomically reject older updates. Keep the delete version through the replay horizon.
Interviewer follow-up
Why not just remove the index row?
Reveal the follow-up answer
Forgetting its version allows an old edit to recreate it.
What the answer must demonstrate: Uses an atomic version comparison and retains deletion evidence.
When may an indexer advance its source position?
Reveal a model answer
After the indexed changes are recoverable. Replaying already applied versions is safe; skipping undurable changes is not.
Interviewer follow-up
What if the log gap is no longer retained?
Reveal the follow-up answer
Rebuild from a newer consistent source snapshot instead of claiming complete replay.
What the answer must demonstrate: Never lets progress outrun recoverable index state.
Why combine a snapshot and search-after?
Reveal a model answer
The snapshot fixes the visible index state, and the last sort tuple gives a stable continuation position without shifting offsets.
Interviewer follow-up
Must a deleted item still appear to preserve the snapshot?
Reveal the follow-up answer
No. Current deletion and permission checks override snapshot membership.
What the answer must demonstrate: Combines stable ordering with current deletion checks.
An indexed match now has different text. What should be returned?
Reveal a model answer
Authorize and load the same current version, then verify it still satisfies the query. Omit a nonmatch; the index may temporarily miss new matches too.
Interviewer follow-up
Can a cached public permission authorize a newly private version?
Reveal the follow-up answer
No. The permission decision must bind the returned content version.
What the answer must demonstrate: Binds content and permission versions and acknowledges recall lag.
Why is reranking twenty recent matches not globally most-liked search?
Reveal a model answer
A highly liked older match may be absent from the retrieved set. Popularity must affect candidate selection or the result must be labeled approximate.
Interviewer follow-up
What must relevance merging define?
Reveal the follow-up answer
Comparable scoring, analyzer versions and a corpus-statistics policy across shards.
What the answer must demonstrate: Recognizes missing candidates and score comparability requirements.
Blank-page exercise · 45 minutes
Build the answer yourself
Design public-post keyword search. Use the three solar documents to demonstrate matching, then handle a late edit after deletion and a missing query shard.
- 0–5 min: agree numbered functional and non-functional requirements for matching, ordering, 300 ms latency, five-second indexing, history, privacy and shard failure.
- 5–12 min: explain postings and the complete single-node path.
- 12–20 min: estimate posting/fanout work and define APIs/data.
- 20–30 min: choose time/document shards, replicas and stable pagination.
- 30–38 min: handle versions, checkpoints and current-result checks.
- 38–45 min: review the final design against the numbered FR/NFR lists, test query/freshness/rebuild behavior and state snapshot, incomplete-index and ranking limits.
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 public post searchDoes an inverted-index match mean the post may be returned?Recall first, then reveal
No. The index supplies possible matching IDs; the current source version and permission checks decide what may be shown.
Terms find; source confirms.
Return to lessonDesign public post searchT103 is deleted at version 12, then an old version-11 edit arrives. What must the index retain and check?Recall first, then reveal
Retain delete version 12 and atomically reject version 11 as older. Removing the document without its version would let the delayed edit recreate it.
Remember the delete’s version.
Return to lessonDesign public post searchHow does the next search page continue without reordering the same results?Recall first, then reveal
Use the same unexpired index snapshot and continue after the last result’s creation time and post ID.
Same view, next position.
Return to lessonFinal revision
Summary and interview notes
An inverted index finds matching post IDs without scanning all posts. Version checks stop stale updates undoing deletes; current source and permission checks decide which results may be returned.
Remember these points
- Separate matching from ranking.
- Keep document terms together and prune historical scope.
- Accept only newer document versions; save progress after the index changes can be recovered.
- Pin page state while still checking current content and permission.
Interview tips
- Use three documents to prove AND/OR behavior.
- Compare candidate-merging bytes with final response bytes.
Important qualifications
Continue after the core interview
Explore the advanced version
The advanced lesson keeps the full detailed design. Use these sections when you want to examine the stronger requirements and failure cases.
- Snapshot watermarks and generation-safe rebuilds
Prove the relationship between source snapshots and multiple change-log positions.
- BM25 and comparable distributed scores
Work through lexical relevance and pinned popularity feature generations.
- Version-bound hydration and filtered pagination
Analyze exact content-version checks and bounded candidate refill.
- Private and semantic search extensions
Add access-aware or embedding candidate generation without losing freshness and privacy contracts.
Technical references
- Elastic: near-real-time searchExplains index refresh and the gap between writing a document and searching it.
- Elastic: paginationDocuments search-after, tie-breakers, and point-in-time pagination.
- Elasticsearch search APIDocuments distributed frequency search options and search execution; external ranking features require separate snapshot semantics.
- Lucene: BM25SimilarityDefines BM25 term-frequency saturation, document-length normalization and inverse document frequency; this versioned reference is not a claim about the latest Lucene release.
- Introduction to Information Retrieval: Okapi BM25Author-hosted explanation of BM25 scoring, term frequency, length normalization and parameter tuning.
Practice marks stay in this browser.