Why does hybrid retrieval (BM25 plus dense vectors) typically outperform either alone in production RAG?
BM25 nails exact-token hits like IDs and proper nouns; dense vectors nail paraphrase and concept matches. Their failure modes are uncorrelated, so fusing the rankings lifts recall on both axes.
Imagine searching a library with two helpers. The first helper has a perfect memory for unusual words. Ask for the book that mentions a specific product code or a rare author's name and they find it instantly, but ask for a book on 'how to cook without dairy' and they get lost if the book actually says 'milk free recipes'. The second helper is great at understanding meaning. They know 'milk free' and 'without dairy' are the same thing, but they sometimes forget which book had that specific product code in chapter seven. The trick is to send both helpers, see what each one brings back, and merge the results. Each one covers the other's blind spot, and almost nothing falls through the cracks.
Detailed answer & concept explanation~7 min readEverything you need to truly understand this topic: intuition, mechanics, step by step explanation, code, formulas, and worked example. Click to expand.
Everything you need to truly understand this topic: intuition, mechanics, step by step explanation, code, formulas, and worked example. Click to expand.
Everything you need to truly understand this topic: intuition, mechanics, step by step explanation, code, formulas, and worked example.
Everything important, quickly.
3 min: contrast lexical versus semantic priors, give concrete queries each one wins on, explain why RRF beats raw score averaging, then close with how a reranker layers on top of the fused candidates.
from collections import defaultdict
def reciprocal_rank_fusion(rankings, k=60, top_k=20):
"""
rankings: dict of retriever_name -> ordered list of doc_ids (best first).
Returns fused top_k by RRF.
"""
scores = defaultdict(float)
for retriever, doc_ids in rankings.items():
for rank, doc_id in enumerate(doc_ids):
scores[doc_id] += 1.0 / (k + rank + 1)
fused = sorted(scores.items(), key=lambda kv: kv[1], reverse=True)
return [doc_id for doc_id, _ in fused[:top_k]]
# Usage in a hybrid RAG pipeline
bm25_hits = bm25_index.search(query, top_k=50) # list of doc ids
dense_hits = vector_index.search(embed(query), top_k=50) # list of doc ids
fused = reciprocal_rank_fusion({
"bm25": bm25_hits,
"dense": dense_hits,
}, k=60, top_k=20)
# Optional: rerank fused with a cross-encoder before passing to the LLM.| Property | BM25 (lexical) | Dense retrieval (semantic) |
|---|---|---|
| Matches on | Exact token overlap with IDF weighting | Cosine similarity of learned embeddings |
| Strong on | Rare tokens, IDs, code, proper nouns | Paraphrase, synonyms, conceptual queries |
| Weak on | Synonyms and paraphrased intent | Specific rare tokens, exact strings |
| Cost at query | Inverted-index lookup, very fast | ANN over vector index, moderate |
| Indexing cost | Tokenize and update postings | Embed each chunk through a model |
| Output | Score per doc, easy to interpret | Cosine in [-1, 1], requires calibration |
Real products, models, and research that use this idea.
- Weaviate's hybrid search combines BM25 and dense vectors with RRF fusion in a single query call; Pinecone and Qdrant expose similar hybrid endpoints.
- pgvector paired with PostgreSQL full text search (or paradedb) is the standard hybrid stack inside an existing Postgres deployment.
- Cohere's reranker is commonly applied on top of fused BM25 plus dense candidates to add precision after recall.
- Elastic's ELSER (learned sparse) sits between BM25 and dense, sparse retrieval with learned weights, increasingly used in production hybrid stacks alongside or instead of BM25.
- OpenAI text-embedding-3-large or Voyage Embed v3 paired with Lucene BM25 inside Elasticsearch or OpenSearch is one of the most shipped 2026 RAG stacks.
What an interviewer would ask next. Try answering before peeking at the approach.
QWhy use Reciprocal Rank Fusion instead of simply averaging scores from BM25 and dense?
QWhere do learned sparse retrievers like SPLADE and ELSER fit in this picture?
Don't say thisRed flags and common mistakes that signal junior thinking. Click to expand.
Red flags and common mistakes that signal junior thinking. Click to expand.
Assuming modern dense embeddings make BM25 obsolete. They do not. Lexical hits on IDs, codes, and rare tokens remain a category where BM25 wins.
The night-before-the-interview bullets. Scan these on the way to the call.
Primary sources. Skim if you want the original framing.
Same topic, related formats. Practice these next.