Reciprocal Rank Fusion (RRF)
Introduction
The last lesson left us with a cliffhanger: hybrid search needs to combine a dense ranking and a sparse (BM25) ranking, but their scores live on incompatible scales, so you can't just average them. This lesson is the elegant answer — and it's almost absurdly simple.
Reciprocal Rank Fusion (RRF) combines any number of ranked lists into one, using only the rank positions — never the raw scores. It's one line of code, needs no normalization and no training, and in its founding paper it beat more complex, learned fusion methods. It's the default 'glue' inside virtually every hybrid-search engine you'll use.
In this lesson you'll learn:
- The RRF formula — and how to write it in six lines
- Why ranks beat scores, and what the k constant does
- The consensus property that makes it work, and weighted RRF
- The honest catch: when Relative Score Fusion actually beats RRF
The Formula
Here it is — the whole algorithm. For a document d, sum a small reciprocal of its rank in each list:
where rank_i(d) is d's 1-based position in list i (1 = top), and k is a small constant (typically 60). A document that doesn't appear in a list simply contributes 0 from that list. Higher total = better. That's it — written out:
def reciprocal_rank_fusion(ranked_lists, k=60, weights=None):
"""Fuse several ranked lists of doc ids into one. ranked_lists[i] is ordered best→worst."""
weights = weights or [1.0] * len(ranked_lists)
scores = {}
for w, docs in zip(weights, ranked_lists):
for rank, doc_id in enumerate(docs, start=1): # rank is 1-BASED
scores[doc_id] = scores.get(doc_id, 0.0) + w / (k + rank)
return sorted(scores, key=scores.get, reverse=True) # docs not in a list simply contribute 0
# Combine the dense and sparse hits from last lesson — we only need the ORDERED ids:
fused = reciprocal_rank_fusion([dense_ids, bm25_ids], k=60)
# That's the whole algorithm. No score normalization, no training, ~6 lines.Notice what's absent: there is no score anywhere in this code. We never touch cosine similarity or BM25 magnitudes — only the order each retriever produced. That single design choice is what makes RRF so robust, as the next section explains.

Why Ranks Beat Scores
It feels like throwing away information to ignore the scores — but it's exactly the point, and it's empirically the right call.
1. Scale-invariance. This is the problem from L96, solved for free. BM25 scores are unbounded positive floats; cosine similarity sits in [−1, 1]. You can't meaningfully add a BM25 of 28.4 to a cosine of 0.81. But rank #1 means the same thing in both lists — "this retriever's best guess." Position is the universal language every ranker speaks, so RRF sidesteps normalization entirely.
2. Robustness to outliers. Score-based fusion is fragile: one document with a freakishly high BM25 score, after min-max normalization, compresses everything else toward zero and dominates the blend. RRF can't be hijacked that way — an outlier is still just rank 1, contributing a modest 1/(k+1).
3. It empirically works. RRF comes from Cormack, Clarke & Büttcher (2009) — the paper's title says it all: "Reciprocal Rank Fusion outperforms Condorcet and individual Rank Learning Methods." They showed that this trivial rank-only formula beat more sophisticated, trained fusion methods. Simple and strong is a rare, wonderful combination.
The k Constant
The one knob in RRF is k, and it controls how much being near the very top matters. Look at the reciprocal at k = 60:
- rank 1 →
1/(60+1) ≈ 0.0164 - rank 2 →
1/(60+2) ≈ 0.0161 - rank 10 →
1/(60+10) ≈ 0.0143
With k=60, ranks 1 and 2 are almost equal — the formula deliberately dampens the advantage of any single list's #1, so a document has to do well across lists to win. Change k and you change that character:
- Small k (e.g. 1): rank 1 →
1/2 = 0.5, rank 2 →1/3 ≈ 0.33. A steep drop-off — the top result of each list dominates. - Large k (e.g. 200): all ranks contribute nearly equally — the formula degrades toward "sum of inverse ranks with flat weights."
k = 60 is the standard default (from the 2009 paper, robust across many benchmarks; it's also the default in OpenSearch and Elasticsearch). Treat it as sensible, not sacred — if you have labeled data, tuning within [40, 80] (or up to ~100 for very long lists) can help. You'll feel exactly what k does in the next section.
See It: The RRF Calculator
Theory becomes obvious when you can turn the knobs. Below are two ranked lists (a dense retriever and a BM25 retriever) over the same documents. Drag k and the per-retriever weights, and watch the fused ranking — and each document's 1/(k+rank) breakdown — recompute live:

Play with it and you'll see the three ideas at once: documents that appear in both lists (the green consensus rows) rise to the top; dropping k toward 1 makes the #1s pull dramatically ahead; and tilting a weight lets you trust one retriever more. The single most important thing to notice is in the next section.
Consensus — the Real Magic
Here's why RRF produces good rankings, not just how. Because each list adds to a document's total, RRF rewards agreement across retrievers.
A document ranked a modest #10 in both lists can outscore a document ranked #1 in only one list and missing from the other.
Walk the math at k=60: the consensus doc gets 1/70 + 1/70 ≈ 0.0286; the one-list star gets 1/61 + 0 ≈ 0.0164. Consensus wins. And that's exactly the behavior you want from hybrid search: a document that both your semantic search and your keyword search consider relevant is a safer bet than one a single method loved and the other never surfaced. RRF turns "multiple retrievers agree" into "ranks near the top."
This is also why RRF generalizes far beyond dense+sparse: feed it the results of N different query rewrites (the multi-query lesson, L100), several embedding models, or any ensemble of rankers, and it surfaces what they collectively trust. It's a universal consensus machine for ranked lists.
Weighted RRF & Using It in Practice
Plain RRF treats every list equally. Often you trust one retriever more — so add a per-list weight:
Set w_dense = 0.6, w_sparse = 0.4 to lean semantic, or the reverse for an identifier-heavy corpus. You almost never implement any of this yourself, though — it's built into the tools:
from langchain.retrievers import EnsembleRetriever
# Production hybrid in 3 lines — RRF is built in.
ensemble = EnsembleRetriever(
retrievers=[bm25_retriever, dense_retriever],
weights=[0.4, 0.6], # WEIGHTED RRF — trust the dense leg a bit more
)
docs = ensemble.invoke("how do I change my sign-in credentials?")
# Runs both retrievers and fuses their ranked lists with reciprocal rank fusion under the hood.
# (Native RRF also ships in OpenSearch, Elasticsearch, Weaviate, Qdrant, …)EnsembleRetriever runs both retrievers and fuses with RRF (and accepts weights). The same algorithm ships natively in OpenSearch, Elasticsearch, Weaviate (rankedFusion), and Qdrant — usually a single parameter on a hybrid query. So in practice RRF is less something you write and more something you configure — but now you understand precisely what that knob is doing.
The Honest Take: RRF Isn't Always Best
Most tutorials present RRF as the answer to fusion. The expert view is more nuanced — and it follows directly from RRF's one design choice.
Because RRF throws away score magnitude, it also throws away real signal when that signal is good. If your dense retriever returns a 0.95 and a 0.71 at ranks 1 and 2, RRF treats them as just "rank 1, rank 2" — it can't tell that #1 was a much stronger match. When your scores are well-calibrated, that's information left on the table.
The alternative is Relative Score Fusion (RSF): normalize each retriever's scores to a common range and combine those. It retains the magnitude information RRF discards. The evidence is real:
- Weaviate switched its default from RRF (
rankedFusion) to RSF (relativeScoreFusion) in v1.24, reporting a ~6% recall improvement in their internal benchmarks, and noting RSF tends to win in multimodal hybrid search.
So which do you use? The honest rule:
- RSF can edge out RRF when both retrievers produce calibrated scores on a comparable scale — and it captures nuance RRF can't.
- RRF wins on stability and operational simplicity — no normalization to get wrong, no fragility to outliers, no per-corpus calibration. For the common case (and especially when you're unsure if your scores are calibrated), RRF is the safe, strong default.
The meta-lesson: fusion method is itself a choice to evaluate, not a settled fact. Start with RRF (it's robust and free); A/B it against RSF on your own retrieval eval if you're chasing the last few points.
🧪 Try It Yourself
Drive the calculator, then reason it through:
- In the widget at k=60, find a document that ranks mid-list in both retrievers. Does it beat a document that's #1 in just one list? Why?
- Slide k from 60 down to 1. What happens to the gap between the top fused result and the rest — and what does that mean about trusting each list's #1?
- Compute by hand (k=60): doc A is rank 1 in dense, absent in BM25; doc B is rank 3 in dense and rank 2 in BM25. Who wins?
- Your dense retriever is far more reliable than BM25 on your data. What do you change in the RRF call — and does it require re-normalizing anything?
- Your two retrievers both output well-calibrated scores on the same 0–1 scale, and you're chasing maximum recall. Is RRF necessarily your best fusion choice?
→ (1) Yes — the consensus doc gets a contribution from both lists (1/70 + 1/70 ≈ 0.0286) vs the one-list star's 1/61 ≈ 0.0164; RRF rewards agreement. (2) The top result's score pulls far ahead (rank 1 → 1/2 at k=1 vs 1/61 at k=60); small k makes each list's #1 dominate the fusion. (3) B wins: 1/63 + 1/62 ≈ 0.0320 > A's 1/61 ≈ 0.0164 — consensus beats a lone #1. (4) Add weights (e.g. [0.7, 0.3] favoring dense) — weighted RRF; no normalization needed (it still operates on ranks). (5) Not necessarily — with calibrated scores, Relative Score Fusion can beat RRF by using the magnitudes RRF discards; A/B them. RRF remains the safer default when calibration is uncertain.
Mental-Model Corrections
- "To fuse rankings, average or add the scores." Scores live on different scales → naive combination collapses to whichever ranker has bigger numbers. RRF fuses by rank and sidesteps this entirely.
- "Ignoring scores throws away information." It throws away fragile, incomparable information — and Cormack 2009 showed rank-only fusion beats normalized-score fusion. (The exception: genuinely calibrated scores — see RSF.)
- "k is a magic number / k tunes relevance." k only controls how steeply top ranks are favored (small k = steep, large k = flat). 60 is a robust default, tunable in [40, 80].
- "RRF favors whichever list ranked a doc highest." It favors consensus — a doc agreed upon by multiple lists outranks a one-list favorite.
- "RRF is always the best fusion method." When scores are calibrated, Relative Score Fusion can win (Weaviate's default since v1.24). RRF wins on robustness and simplicity — the common case.
- "RRF only works for dense + sparse." It fuses any number of ranked lists — multi-query, multiple models, any ensemble.
Key Takeaways
- RRF(d) = Σ 1/(k + rankᵢ(d)) — fuse ranked lists by rank position, not score. ~6 lines, no normalization, no training (Cormack et al., 2009), and it beat fancier methods.
- Why ranks: scale-invariant (solves the BM25-vs-cosine problem), robust to outliers, and empirically strong.
- k (≈60) dampens top-rank dominance: small k → #1s dominate, large k → ranks flatten. Default 60 is sensible, not sacred (tune 40–80).
- Consensus is the magic: a doc ranked decently in multiple lists beats a one-list favorite — exactly what you want from hybrid/ensemble retrieval. Weighted RRF (
Σ wᵢ/(k+rankᵢ)) lets you trust a retriever more. - It's native everywhere (LangChain
EnsembleRetriever, OpenSearch, Elasticsearch, Weaviate, Qdrant) — usually one parameter. - Honest catch: RRF discards score magnitude; when scores are calibrated, Relative Score Fusion can beat it (Weaviate default since v1.24, ~6% recall) — but RRF wins on stability & simplicity. Evaluate fusion as a choice.
- Next: reranking — a cross-encoder reads each query–document pair to reorder the top results with far higher precision than any first-stage retriever.