AAI Agentic Handbook
Ranking & Generation

Reciprocal Rank Fusion (RRF)

Mid levelReviewed Oct 2026In 2 interview questionsIn 3 posts

Reciprocal Rank Fusion: merge ranked lists by summing 1/(k + rank) per document, k = 60. Uses ranks only, so no score calibration is needed.

In plain English

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.

Analogy

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.

Common pitfall

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.

How it actually works

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).

What I'd ship on day one

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.

Worked example

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.

In the interview

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
  1. clause-14.2
  2. clause-3.1
  3. annex-B
  4. clause-9
Dense
  1. clause-3.1
  2. clause-7.4
  3. clause-14.2
  4. annex-C
RRF, k = 60
  1. clause-3.1
  2. clause-14.2
  3. clause-7.4
  4. annex-B
  5. clause-9
  6. annex-C
Appearing high in both lists beats being first in one
DocBM25 rankDense rankRRF score
clause-3.1211/62 + 1/61 = 0.03252
clause-14.2131/61 + 1/63 = 0.03226
clause-7.4—21/62 = 0.01613
annex-B3—1/63 = 0.01587
Worked RRF scores; absent from a list contributes 0

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

WhenDo this
No labelled queries yet, or retrievers change oftenRRF, k = 60
≥ 100 labelled queries and a stable embedding modelA/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

Time to scale up when…
  • 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
What to monitor
  • 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

What users saw

Searches for exact SKU codes in an e-commerce catalog started returning look-alike products first

How it was caught

Zero-click rate on queries matching ^[A-Z]{2}-\d{5}$ rose from 8% to 31% in the search dashboard

Root cause

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

Fix

Regex router sends code-shaped queries to BM25 only; other queries keep RRF. Added an 'exact identifier' slice to the golden set

Sources

  1. Reciprocal Rank Fusion outperforms Condorcet and individual Rank Learning Methods — Cormack, Clarke & Büttcher, 2009paper
  2. An Analysis of Fusion Functions for Hybrid Retrieval — Bruch, Gai & Ingber, 2022paper

Check yourself

Three questions. Every option is explained once you answer.

1ConceptWhy does RRF need no score calibration between BM25 and dense retrieval?
2ScenarioNo labelled queries yet, and the embedding model will change monthly during the pilot. How do you fuse hybrid results?
3CalculationWith k = 60, doc A is rank 1 in BM25 and absent from dense; doc B is rank 3 in both lists. Which ranks higher after RRF?