What RRF is
Hybrid search produces two ranked lists, one from keyword search and one from vector search, and their scores cannot be compared directly: a BM25 score is unbounded and shifts with the corpus, a cosine similarity sits between -1 and 1. RRF sidesteps the problem by throwing the scores away and using only the positions.
A document near the top of both lists collects from both and beats a document that only one list liked. That is the property you want, and it is why RRF suppresses the semantic noise a pure vector index produces.
It needs no training data and no score calibration, which is why it is the usual fusion method in hybrid search; Elasticsearch ships it as an rrf retriever. The trade-off is that it discards information: a leg that is very sure about its top result gets the same credit as one that barely preferred it.
The formula, worked through
For each document d, sum over every list i it appears in: w_i / (k + rank_i(d)), where w_i is an optional weight per list and k is a constant, conventionally 60. Rank here starts at 0.
A and C appear in both lists and rise to the top; B and D each had one supporter. The fused scores are small numbers whose only meaning is their order.
Why k is 60
k controls how fast credit decays down a list. A small k makes rank 1 worth far more than rank 5; a large k flattens toward equality. At 60, the gap between rank 0 and rank 9 is about 15%, which is deliberately gentle because positions from two incomparable systems are noisy. 60 is the value from the original RRF paper and the Elasticsearch default.
Common pitfalls
- Tuning `k` first. When hybrid results look wrong, the weights or the inputs are almost always the problem, not the constant.
- Short input lists. Fusing two top-10 lists gives fusion nothing to promote. Feed it a larger candidate pool from each leg.
- Reading the fused score as confidence. An RRF score of 0.016 says nothing about relevance. Keep each leg's own score for that.
- One leg silently empty. If the keyword leg returns zero hits, RRF still produces a list and the failure is invisible.
How SarvCrawl handles it
SarvCrawl's hybrid search fuses BM25 and vector results with RRF at k = 60, computed in the application rather than with Elasticsearch's rrf retriever, which is not in the Basic licence tier. That keeps self-hosted and hosted search identical. Leg weights are set by query shape and never go to zero.
Each hit carries rrf_score plus bm25_score, knn_score and match_via, so you can see which leg produced it. More in hybrid search in production and the search API.
Frequently asked questions
What does the k in reciprocal rank fusion do?
It sets how quickly a document's contribution falls with rank. Larger values flatten the difference between top and lower positions. 60 is the standard default.
Why not just add the BM25 and vector scores?
They are on different scales and the BM25 range changes with the corpus and query, so any normalisation becomes a per-corpus parameter that drifts. RRF uses ranks only and needs no normalisation.
Can RRF fuse more than two lists?
Yes. The formula sums over any number of lists, so it can merge several retrievers or several query rewrites.