Skip to main content
Semantic search goes beyond keyword matching to understand meaning and intent. Traditional keyword search is like looking up a word in a dictionary — it finds exact matches but misses everything else. Semantic search is like asking a knowledgeable friend — “show me things about vacation policy” will find documents about “PTO guidelines,” “time off procedures,” and “leave of absence rules” even if they never use the word “vacation.” This is the retrieval backbone of every RAG system, search feature, and recommendation engine in modern AI.

Search Methods Comparison


BM25 Implementation

BM25 (Best Match 25) is the algorithm behind Elasticsearch, Solr, and every traditional search engine you’ve ever used. It is a probabilistic ranking function that scores documents based on term frequency (how often the query words appear) weighted by inverse document frequency (rare words matter more than common ones). Think of it as “smart keyword matching” — it handles the math that makes “rare important word” rank higher than “common filler word.” Despite being decades old, BM25 remains unbeatable for exact-match queries like product SKUs, error codes, and proper nouns:

Semantic Search with Embeddings


Neither BM25 nor semantic search is universally better — they have complementary strengths. BM25 excels at exact matches (error codes, function names, acronyms) while semantic search excels at meaning (synonyms, paraphrases, conceptual similarity). Combining them consistently outperforms either alone. The only question is how to weight them. Combine BM25 and semantic search for best results:

Reciprocal Rank Fusion (RRF)

RRF is the industry standard for merging multiple ranked lists. Unlike weighted averaging (which requires score normalization), RRF only uses rank positions, making it robust across different scoring scales. The formula is simple: for each document, sum 1/(k + rank) across all rankings. Documents that appear near the top in multiple lists get the highest combined score.

Reranking

Retrieval is fast but approximate. Reranking is slow but precise. The two-stage pattern exploits this: retrieve 100 candidates cheaply (milliseconds), then rerank the top 100 with a powerful cross-encoder model that reads the query and each document together (seconds). A cross-encoder sees the query-document pair simultaneously, so it catches subtle relevance signals that bi-encoder similarity misses. Rerank initial results with a more powerful model:

Query Expansion

Improve recall by expanding queries:

Contextual Retrieval

Add context to chunks before embedding:

Search Pipeline


Performance Comparison

These numbers are representative across multiple benchmarks. The takeaway: each layer of sophistication buys real recall improvement, but at increasing cost and latency. Choose based on your quality requirements.

Search Failure Modes and Fixes

Understanding why search fails is more valuable than understanding why it succeeds. These are the failure patterns you will encounter in production: Edge case — queries with embedded constraints: “What is our refund policy for orders over 500?"theembeddingcapturesthetopic(refundpolicy)butnotthenumericconstraint(500?" -- the embedding captures the topic (refund policy) but not the numeric constraint (500). Semantic search finds refund policy docs but cannot filter by dollar amount. Fix: extract structured constraints with an LLM before search, apply them as metadata filters, and use semantic search only for the topical component.

What is Next

Context Window Management

Learn to manage context windows effectively with compression and optimization

Interview Deep-Dive

Strong Answer:
  • The answer depends on the content and query patterns, but for an internal knowledge base I would almost certainly end up with hybrid search. Here is the reasoning: internal docs contain a mix of natural language (policy documents, onboarding guides) and highly specific terms (project codenames, internal tool names, error codes, Jira ticket IDs). Pure semantic search excels at the first category but completely misses exact-match needs. Pure BM25 handles exact terms but fails when someone asks “how do I take time off” and the document says “PTO request procedure.”
  • I would start by building both pipelines independently and running a retrieval evaluation. Take 50-100 real user queries from search logs (or create them manually if no logs exist), have domain experts label the top 5 relevant documents for each query, then measure Recall@10 for BM25 alone, semantic alone, and hybrid at different alpha values. In my experience, hybrid consistently beats either individual method by 10-25% on Recall@10 for mixed-content corpora.
  • For tuning the alpha weight, I would start at 0.7 semantic / 0.3 BM25 as a default. Then I would segment queries into categories — exact-match queries (error codes, names), conceptual queries (how-to, explanations), and mixed. Tune alpha per category if your system can classify query type, or find the alpha that maximizes recall across the blended query set. I have found that alpha between 0.5 and 0.7 works for most knowledge bases. Technical documentation with lots of code and acronyms benefits from lower alpha (more BM25 weight), around 0.4-0.5.
  • The practical implementation detail most people miss: score normalization. BM25 scores and cosine similarity scores are on completely different scales. BM25 can range from 0 to 20+, while cosine similarity is 0 to 1. You must normalize both to the same range before combining, or the raw BM25 scores will dominate regardless of your alpha. Min-max normalization within each result set is the simplest approach; Reciprocal Rank Fusion (RRF) avoids the normalization problem entirely by using rank positions instead of scores.
Follow-up: You mentioned RRF avoids the normalization problem. When would you prefer RRF over weighted score combination, and what is the downside of RRF?RRF is more robust when combining rankings from systems with incompatible score distributions — which is exactly the BM25 + semantic case. It only uses rank positions, so it does not care about score scales. The constant k=60 from the original paper rarely needs tuning, which makes it operationally simpler. The downside is that RRF throws away magnitude information. If semantic search returns a document with 0.99 similarity (a near-perfect match) and another at 0.72, RRF treats the gap between rank 1 and rank 2 the same regardless. Weighted score combination preserves that signal — a 0.99 match contributes much more than a 0.72 match. In practice, this matters when you have a “golden” document that is a clear best match. RRF can dilute that signal by boosting a document that ranked high in BM25 but is semantically mediocre. I would use RRF as the default for simplicity and switch to weighted combination only if evaluation shows that top-1 precision matters significantly for your use case.
Strong Answer:
  • The two-stage pattern exists because retrieval speed and ranking quality are fundamentally at odds. A bi-encoder (used in embedding-based retrieval) encodes the query and each document independently, which means document embeddings can be pre-computed and indexed. Searching 1 million pre-computed embeddings takes milliseconds using approximate nearest neighbor (ANN) indexes. A cross-encoder (used in reranking) encodes the query and document together as a single input, which means it must do a forward pass for every query-document pair at query time. Running a cross-encoder against 1 million documents would take hours.
  • The two-stage approach exploits this asymmetry: use the fast but approximate bi-encoder to retrieve 50-200 candidates from the full corpus (milliseconds), then use the slow but accurate cross-encoder to rerank only those candidates (hundreds of milliseconds). You get cross-encoder quality at bi-encoder speed. In benchmarks, this pattern typically improves Recall@10 by 5-15% over retrieval alone, with only 100-200ms added latency.
  • The critical tuning parameter is the retrieval set size — how many candidates you pass to the reranker. Too few (say 10) and the reranker cannot recover relevant documents that the bi-encoder missed. Too many (say 1000) and the reranker becomes the latency bottleneck. I typically start with 100 candidates and measure recall improvement as I increase to 200, 500. There are diminishing returns — going from 50 to 100 candidates usually helps significantly, but going from 200 to 500 rarely does.
  • The other nuance is that bi-encoders and cross-encoders often disagree on what is relevant, and that disagreement is exactly where the value lives. The bi-encoder might rank a document at position 50 because it captures semantic similarity but misses a subtle relevance signal. The cross-encoder, seeing both query and document together, catches that signal and promotes it to position 3. Documents that both agree on (top 5 in both) are slam-dunk relevant. Documents where they disagree are the interesting cases.
Follow-up: In production, the reranker adds 150ms of latency. How would you decide if that latency is justified, and what alternatives exist if it is not?Measure the impact on end-user metrics, not just retrieval metrics. If you are building a RAG system, compare the LLM answer quality with and without reranking using a blind evaluation. If the LLM answers are 10% better with reranking, and your users are paying customers making important decisions based on those answers, 150ms is trivially worth it. If you are building a casual search feature and users care more about speed than precision, skip it. Alternatives to a full cross-encoder reranker include: Cohere Rerank API (managed, fast, pay-per-call), ColBERT-style late interaction models that are faster than cross-encoders but more accurate than bi-encoders, or a lightweight “reranker” that is just an LLM prompt asking “which of these 10 documents best answers the query?” — surprisingly effective for small candidate sets and you already have the LLM in your pipeline.
Strong Answer:
  • HyDE is a query expansion technique where instead of embedding the user’s query directly, you first ask an LLM to generate a hypothetical answer to the query, then embed that hypothetical answer and use it for retrieval. The intuition is that a hypothetical answer is closer in embedding space to the actual relevant documents than a short question is. A query like “how to handle database connection pooling” is a question, but the relevant document is an explanation — they live in different parts of embedding space. A hypothetical answer about connection pooling is an explanation, so it lands closer to the real document.
  • In benchmarks, HyDE improves recall by 10-20% on knowledge-intensive queries where the query and the documents have different linguistic structures. It works best when the query is short and abstract (“best practices for microservice auth”) and the documents are long and detailed.
  • When it goes wrong: the LLM can hallucinate facts in the hypothetical answer that steer retrieval in the wrong direction. If the query is “What is the company’s remote work policy?” and the LLM generates a hypothetical answer about a flexible remote policy when the actual policy is strict in-office, the embedding of the hallucinated answer may retrieve documents about flexible work rather than the actual policy. You are searching for what the LLM thinks the answer is, not what the answer actually is.
  • It also adds latency and cost: one full LLM call to generate the hypothetical document before you even start retrieval. For a search feature where users expect sub-second results, this 500ms+ overhead is significant. And you are paying for an LLM generation on every query just for retrieval, before you even get to the answer-generation step.
  • I would use HyDE selectively: for complex analytical queries where recall is more important than latency (research assistants, legal search), and skip it for simple factual queries where standard embedding works fine. A good heuristic: if the query is under 10 words and looks like a keyword search, skip HyDE. If it is a full question or a complex information need, try HyDE.
Follow-up: Could you combine HyDE with hybrid search, and how would that interaction work?Yes, and it is actually a strong combination. Use HyDE for the semantic arm of hybrid search — embed the hypothetical answer for vector similarity — while keeping BM25 on the original query for the keyword arm. This way, the semantic search benefits from the hypothetical document being closer to relevant passages, while BM25 still catches exact-match terms from the original query that might be lost in the LLM-generated hypothesis. The BM25 arm acts as a safety net against HyDE hallucination — if HyDE steers semantic search toward the wrong topic, BM25 can still surface the right document based on keyword overlap. In practice, I have seen this combination outperform both standard hybrid and HyDE-only approaches, but it doubles your retrieval cost (LLM call + embedding call + BM25), so it is only justified when retrieval quality is paramount.
Strong Answer:
  • First, categorize the failures. Pull the 15% of bad-result queries and classify them: Are they exact-match queries where BM25 should dominate? Conceptual queries where semantic should dominate? Multi-intent queries? Queries in a language or jargon the embedding model was not trained on? The distribution of failure types tells you where to focus.
  • Second, check the retrieval stage independently from the rest of the pipeline. For each failing query, look at what the retriever returned (the raw document chunks) before any reranking or LLM processing. If the relevant document is not in the top 100 retrieved candidates, the problem is retrieval. If it is in the top 100 but ranked at position 80, the problem is ranking. If it is ranked at position 3 but the LLM still gave a bad answer, the problem is downstream — not search.
  • Third, for retrieval failures, check the chunking. The number one cause of bad search results in my experience is bad chunking: a relevant passage got split across two chunks and neither chunk is self-contained enough to rank well. Pull the actual chunk that should have been retrieved and examine it. Does it make sense in isolation, or does it start with “This approach…” with no antecedent? If chunking is the issue, increase chunk overlap, switch to semantic-boundary chunking, or add contextual retrieval (prepending a summary to each chunk).
  • Fourth, check the embedding quality. Take a failing query and its known-relevant document, embed both, and compute their cosine similarity. If similarity is below 0.7, the embedding model is not capturing the semantic relationship. This happens with domain-specific jargon, acronyms, or niche technical content. Solutions: fine-tune the embedding model on your domain data, add synonyms to the query via query expansion, or switch to a larger embedding model.
  • Fifth, check the hybrid weighting. It is possible your alpha is wrong for the query distribution. Run a sweep of alpha values (0.3, 0.5, 0.7) on the failing queries and see if a different weight recovers the relevant documents. If the failing queries are mostly exact-match but alpha is 0.8 (heavy semantic), you need to lower alpha or implement query-type-aware weighting.
Follow-up: You have identified that chunking is the root cause for 60% of the failures. How do you fix chunking without re-processing your entire 500K document corpus?You do not avoid re-processing — you plan for it. Chunking changes require re-embedding because different text produces different vectors. The real question is how to do it efficiently and without downtime. I would implement versioned indexes: create a new index (documents_v2) with the improved chunking strategy, embed in batch overnight, then atomically swap the search endpoint to point to the new index. Keep the old index available for rollback. For the batch re-processing itself, use the embedding API’s batch endpoint (up to 2048 texts per call) and parallelize across workers. For 500K documents with an average of 5 chunks each, that is 2.5M embeddings — at 2048 per batch, about 1,200 API calls. With parallelism, this takes a few hours and costs roughly $2-5 with text-embedding-3-small. The key lesson: always design your vector store for re-indexing from day one, because you will change your chunking strategy at least twice.