Reciprocal Rank Fusion (RRF)
Reciprocal Rank Fusion: merge ranked lists by summing 1/(k + rank) per document, k = 60. Uses ranks only, so no score calibration is needed.
When you search two ways at once, say keyword search (BM25) and meaning search (Dense retrieval), you get two ranked lists whose scores are on different scales. RRF ignores the scores and looks only at positions: a document gets 1/(60 + its rank) from each list it appears in, and you sort by the total. Documents that rank well in both lists rise to the top.
Two film critics each publish a top-10 list. Instead of comparing their star ratings, you give each film points for how high it appears on each list and add them up.
Fusing only the top 10 of each list. A document at rank 15 in both lists can deserve a top-5 fused spot; fuse 50–100 per list, then rerank.
Score(d) = Σ over lists of 1 / (k + rank_i(d)), with ranks starting at 1 and documents absent from a list contributing 0. Cormack, Clarke and Büttcher (2009) found k = 60 robust across TREC runs; k damps the gap between rank 1 and rank 2 (1/61 vs 1/62) so one list cannot dominate. Because it uses ranks only, RRF is immune to BM25 scores of 5–40 versus cosine scores of 0.2–0.9, needs no training data, and is the default fusion in Elasticsearch/OpenSearch hybrid queries, Qdrant's Query API, Weaviate and Azure AI Search. The cost of that robustness is lost information: a dense hit with cosine 0.91 and one with 0.52 at adjacent ranks count the same. With 50–200 labelled queries, a tuned convex combination usually beats it by 1–3 nDCG points (Bruch et al. 2022).
k = 60; fuse the top 50–100 from each retriever; dedupe by document or chunk id before scoring; send the fused top 100 to a Reranker. Use the engine's native implementation (Elasticsearch/OpenSearch rrf retriever, Qdrant fusion: rrf) rather than fusing in application code.
Contract search for 'termination for convenience notice period'. BM25 returns clause-14.2, clause-3.1, annex-B, clause-9; dense returns clause-3.1, clause-7.4, clause-14.2, annex-C. RRF (k = 60): clause-3.1 = 1/62 + 1/61 = 0.03252; clause-14.2 = 1/61 + 1/63 = 0.03226; clause-7.4 = 1/62 = 0.01613; annex-B = 1/63 = 0.01587. clause-3.1 wins because it is near the top of both lists, even though BM25 ranked clause-14.2 first.
Say: RRF with k = 60 over 50–100 candidates from each retriever is my day-one fusion because it needs no labels and no score normalisation. Once I have ~100 labelled queries I test a tuned convex combination against it and switch only if it wins by more than the confidence interval. Expect: 'Why 60?' It is empirical from TREC; smaller k sharpens the top, and the result is insensitive between roughly 20 and 100.
BM25
- clause-14.2
- clause-3.1
- annex-B
- clause-9
Dense
- clause-3.1
- clause-7.4
- clause-14.2
- annex-C
RRF, k = 60
- clause-3.1
- clause-14.2
- clause-7.4
- annex-B
- clause-9
- annex-C
| Doc | BM25 rank | Dense rank | RRF score |
|---|---|---|---|
| clause-3.1 | 2 | 1 | 1/62 + 1/61 = 0.03252 |
| clause-14.2 | 1 | 3 | 1/61 + 1/63 = 0.03226 |
| clause-7.4 | — | 2 | 1/62 = 0.01613 |
| annex-B | 3 | — | 1/63 = 0.01587 |
Numbers worth knowing
- Default k
- 60Cormack et al. 2009; results stable for k ≈ 20–100
- Max per-list contribution
- 1/61 ≈ 0.0164rank 1 with k = 60
- Gap vs tuned convex combination
- 1–3 nDCG@10 pointsin favour of the tuned fusion, with 50–200 labelled queries (Bruch et al. 2022)
How to decide
| When | Do this |
|---|---|
| No labelled queries yet, or retrievers change often | RRF, k = 60 |
| ≥ 100 labelled queries and a stable embedding model | A/B a min-max convex combination against RRF on the golden set |
| One retriever is clearly weak on a query class (IDs, error codes) | Weighted RRF (e.g. 1.5× on BM25) or route that class to BM25 only |
Cost maths
Fusion itself is O(n log n) over a few hundred ids: microseconds. The real cost is running two retrievers: a BM25 index adds ~30–50% storage over raw text and one extra query (5–20 ms) per request. At 10M chunks that is typically one more node, not a new cluster.
Operating it
- Golden set reaches 100+ labelled queries: test a tuned fusion
- A query class (part numbers, statute citations) loses > 10 points of recall under RRF vs BM25 alone
- Third retriever added (e.g. SPLADE): re-validate k and per-list depth
- Overlap@20 between the two lists; alert if it drops below 10% (one retriever broken or index stale)
- Share of final top-5 contributed by each retriever; alert on a > 25-point shift week over week
- Per-query-class recall@50 (codes, natural language, long queries); alert on a > 5-point drop
When it goes wrong
Searches for exact SKU codes in an e-commerce catalog started returning look-alike products first
Zero-click rate on queries matching ^[A-Z]{2}-\d{5}$ rose from 8% to 31% in the search dashboard
RRF gave the dense list equal weight; for opaque codes the dense list was noise, and its rank-1 junk tied with BM25's exact match
Regex router sends code-shaped queries to BM25 only; other queries keep RRF. Added an 'exact identifier' slice to the golden set
Related terms
Where it shows up
Sources
- Reciprocal Rank Fusion outperforms Condorcet and individual Rank Learning Methods — Cormack, Clarke & Büttcher, 2009paper
- An Analysis of Fusion Functions for Hybrid Retrieval — Bruch, Gai & Ingber, 2022paper
Check yourself
Three questions. Every option is explained once you answer.