What Is Reciprocal Rank Fusion?

Reciprocal rank fusion

Reciprocal rank fusion (RRF) is a method for combining several ranked lists into one ranking using only each item’s position in each list. In AI retrieval, RRF is a common way to merge keyword and vector search results, whose scores are on different scales and cannot be added directly.

For example, suppose a keyword retriever ranks chunk A first and chunk B second, and a vector retriever ranks chunk B first and chunk C second. With the standard constant k = 60, chunk B scores 0.03252 and chunk A scores 0.01639. Chunk B ranks first in the fused list without topping the keyword list, because both retrievers found it relevant.

Try Arize AX

Build better agents with Arize

Trace, evaluate, and learn. Build agents that work with Arize AX and start tracing your runs today.

Prefer open source? Try Arize Phoenix for self-hosted, open source agent observability.

Why reciprocal rank fusion matters for retrieval

Many retrieval systems run more than one search for each query. A typical retrieval-augmented generation (RAG) pipeline pairs keyword search with vector search, so it can catch both exact terms and paraphrases.

Those searches produce scores that do not share a scale. A BM25 score is unbounded, while a cosine similarity falls between -1 and 1. Adding them directly lets one system drown out the other.

RRF is useful for:

  • Building hybrid search: Merge keyword and vector results without calibrating their scores.
  • Combining several retrievers: Fuse results from different indexes, query rewrites, or embedding models.
  • Rewarding agreement: Promote items that several retrievers rank well, even if none ranks them first.
  • Keeping fusion predictable: Get stable behavior when one system’s scores drift from query to query.

For more on combining retrieval methods, see hybrid search.

How reciprocal rank fusion works

RRF replaces each item’s score with a value based on its rank. Then it adds those values across all the lists.

Step 1: Rank each list. Each retriever returns its candidates in order, starting at rank 1.

Step 2: Convert each rank to a contribution. An item at rank r in one list contributes 1/(k + r) from that list. Higher-ranked items contribute more.

Step 3: Add the contributions. An item’s fused score is the sum of its contributions from every list that returned it.

Step 4: Sort by fused score. The highest total ranks first in the merged list.

If an item is missing from a list, it gets no contribution from that list. That is the same as treating its rank there as infinite.

The formula and the k = 60 value come from Cormack, Clarke, and Büttcher’s 2009 work on reciprocal rank fusion. Elastic’s reciprocal rank fusion reference documents a production implementation and restates the same formula.

How to calculate an RRF score

Formula:

RRFscore(d) = sum over every ranking r in R of 1 / (k + rank_r(d))

Where:

  • d is the document or chunk being scored.
  • R is the set of ranked lists being fused.
  • rank_r(d) is the position of d in ranking r, starting at 1.
  • k is a constant that controls how much the top ranks dominate. A value of 60 is the widely reported default.

What the constant k does

A larger k flattens the difference between ranks. With k = 60, rank 1 contributes 1/61 and rank 2 contributes 1/62, which are nearly the same.

A smaller k makes the top of each list dominate. With k = 0, rank 1 contributes 1.0 and rank 2 contributes 0.5, so a single first-place finish counts for much more.

Candidate depth matters too. Returning 5 candidates per list and returning 100 per list produce different fused lists, even at the same k.

Calculate RRF in Python

The following function fuses any number of ranked lists using only the standard library. It runs the opening example at k = 60 and at k = 0 to show how the constant changes the scores.

def rrf(rankings, k=60):
    scores = {}
    for ranking in rankings:
        for rank, doc in enumerate(ranking, start=1):
            scores[doc] = scores.get(doc, 0.0) + 1 / (k + rank)
    return sorted(scores.items(), key=lambda item: item[1], reverse=True)

lexical = ["A", "B"]
vector = ["B", "C"]

for k in (60, 0):
    fused = rrf([lexical, vector], k=k)
    print(f"k={k}: " + ", ".join(f"{doc} {score:.5f}" for doc, score in fused))

Output:

k=60: B 0.03252, A 0.01639, C 0.01613
k=0: B 1.50000, A 1.00000, C 0.50000

Worked example: fusing keyword and vector results

Here is the opening example as a table, with k = 60.

Chunk Keyword rank Vector rank RRF score Fused rank
A 1 not returned 1/61 = 0.01639 2
B 2 1 1/62 + 1/61 = 0.03252 1
C not returned 2 1/62 = 0.01613 3

Chunk B wins because both retrievers returned it near the top. Its two contributions add up to roughly twice what A or C earns from a single list.

Chunk A edges out chunk C. A was first in its only list, while C was second in its only list.

At k = 0 the order stays the same, but the gaps widen. B scores 1.5, A scores 1.0, and C scores 0.5. In a larger list, that change could reorder results, which is why you should report k with any result.

How to measure and debug RRF results

Judge the fused list directly, using the same metrics you would use for any ranked retrieval.

  • Finding one known answer: Mean reciprocal rank rewards placing the single correct chunk early.
  • Grading the whole list: NDCG uses graded relevance across every position.
  • Checking the top results: Precision@K shows how many of the top K results are relevant.

When a known relevant chunk drops after fusion, check its rank in each source list.

  • Ranked 40th everywhere: This is a candidate-depth problem. Neither retriever placed it high enough to compete.
  • Ranked 2nd in one list and missing from the fused output: Check the merge step or deduplication logic.

For the wider frame around these checks, see retrieval quality.

Limitations of reciprocal rank fusion

RRF is simple and cheap, but it has known blind spots.

  • Equal trust by default: A weak retriever contributes as much rank evidence as a strong one unless you add weights.
  • No recovery of missing items: RRF cannot promote an item that no retriever returned. Candidate recall sets the ceiling.
  • Lost score information: A retriever that is very confident and one that is barely confident look the same at the same rank.
  • Duplicates: Near-identical chunks can occupy several fused slots.
  • Sensitivity to settings: Results depend on k, candidate depth, and which lists were fused.

When you claim an improvement from RRF, report k, the candidate depth, and the lists you fused.

Reciprocal rank fusion vs. score fusion

Score fusion normalizes each system’s scores and adds them. This keeps information about how strongly each system preferred an item. It also needs calibration for each query, because score ranges shift.

RRF throws away score magnitude and keeps only order. It is cruder, and it is more predictable when scores drift.

Neither is better in every case. Compare both on judged queries from your own workload.

Reciprocal rank fusion vs. reranking

RRF is not a reranker. It merges lists cheaply, using only positions.

A reranking model, such as a cross-encoder, reads each chunk alongside the query and judges its relevance directly. A pipeline can use RRF to merge candidates first and then rerank the merged list.

Frequently asked questions

Why is k usually set to 60 in RRF?

It is the value reported in the original 2009 RRF paper, and it has stayed a common default. Treat it as a starting point and validate it on your own judged queries.

Does RRF need normalized scores?

No. RRF uses ranks only, so you can merge BM25 scores and cosine similarities without putting them on one scale. That is its main convenience.

Can RRF fix poor retrieval?

No. If the right chunk is in no candidate list, fusion cannot create it. Improve indexing, query handling, or candidate depth first, then tune fusion.

Should every list get equal weight in RRF?

Not necessarily. Weighted RRF variants scale each list’s contribution. Add weights only when measurements show one retriever is consistently more reliable.

How many candidates should each retriever return before fusion?

There is no single right number. Deeper lists give fusion more to work with, but they change the fused order. Choose a depth, measure it on judged queries, and report it.

Putting reciprocal rank fusion to work

Start with k = 60 and equal weights. Fuse the keyword and vector lists you already have.

Build a set of judged queries that includes exact identifiers and paraphrased questions. Measure each retriever alone and the fused list on the same set.

Record k, candidate depth, and the source rank of every fused result. In a retrieval-augmented generation (RAG) system, tools such as Arize Phoenix can trace each retrieval step, so you can see where a relevant chunk ranked before and after fusion.

Example

A keyword retriever ranks chunk A 1st and chunk B 2nd, and a vector retriever ranks B 1st and chunk C 2nd. With k = 60, B scores 1/62 + 1/61 = 0.03252, while A scores 1/61 = 0.01639 and C scores 1/62 = 0.01613. B ranks first in the fused list because both retrievers found it relevant.

Don’t ship vibes.

Arize gives AI teams observability and evals to understand and improve agent performance.