Introduction

Every time you type a query into a search engine, the engine must decide in milliseconds which of millions of documents is most relevant. The simplest idea — count how many times your query words appear in each document — turns out to be surprisingly wrong.

BM25 (Best Match 25) is the probabilistic ranking function that fixed those problems. Proposed by Stephen Robertson and colleagues at City University London around 1994 and refined through the TREC evaluation campaigns, it remained the gold-standard retrieval formula for two decades and is still the default scorer in Elasticsearch, Apache Lucene, and Apache Solr today.

Two ideas do most of the work:

  • Term frequency saturation. The first few times a query word appears in a document, it is strong evidence of relevance. The hundredth time adds almost nothing. BM25 squashes diminishing returns with a curve controlled by a parameter k1k_1 (typically 1.2 to 2.0).
  • Length normalization. A long document naturally contains more words, so it will mention your query term more often by chance. BM25 divides the raw count by the document length (relative to the corpus average), controlled by a parameter bb (typically 0.75).

These two corrections transform a naive word-count into a ranking function that matches human relevance judgments far better — and the math behind them traces back to a probabilistic model of how humans actually decide what is relevant.

Try It

The demo below ranks four short documents against your query using both raw term frequency (TF) and BM25. Type a word that appears frequently in some documents, then watch how the two methods disagree — BM25 is less impressed by sheer repetition and more careful about document length.

<!-- {{c_demo_intro}} -->
<div class="controls">
  <input id="query" type="text" placeholder="{{placeholder_query}}" value="search" />
  <div class="sliders">
    <label>k<sub>1</sub> = <span id="k1val">1.2</span>
      <input id="k1" type="range" min="0" max="3" step="0.1" value="1.2" title="{{title_k1}}" />
    </label>
    <label>b = <span id="bval">0.75</span>
      <input id="b" type="range" min="0" max="1" step="0.05" value="0.75" title="{{title_b}}" />
    </label>
  </div>
</div>
<div id="results" class="results"></div>
<p class="legend">{{legend_text}}</p>
/* {{c_base_styles}} */
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; padding: .5rem; }
.controls { display: flex; flex-direction: column; gap: .5rem; margin-bottom: .8rem; }
input[type="text"] { font: 15px system-ui; padding: .4rem .7rem; border: 1px solid #cdd; border-radius: 8px; width: 100%; }
.sliders { display: flex; gap: 1rem; flex-wrap: wrap; }
label { font-size: .85rem; color: #555; display: flex; flex-direction: column; gap: .2rem; }
input[type="range"] { width: 130px; }
/* {{c_result_styles}} */
.results { display: flex; flex-direction: column; gap: .55rem; }
.doc-card { border-radius: 10px; padding: .55rem .8rem; border: 1px solid #dde; background: #f8fafc; }
.doc-title { font-weight: 700; font-size: .9rem; margin-bottom: .25rem; }
.bar-row { display: flex; align-items: center; gap: .5rem; font-size: .8rem; margin: .18rem 0; }
.bar-label { width: 46px; color: #666; flex-shrink: 0; }
.bar-track { flex: 1; height: 10px; background: #e4e8f0; border-radius: 5px; overflow: hidden; }
.bar-fill { height: 100%; border-radius: 5px; transition: width .3s; }
.bar-tf   { background: #94a3b8; }
.bar-bm25 { background: #1d3557; }
.bar-val { width: 40px; text-align: right; color: #444; }
.legend { font-size: .78rem; color: #777; margin-top: .6rem; line-height: 1.5; }
// Code not found

Notice that a very long document stuffed with your query word rises to the top under raw TF but gets pulled back down by BM25's length penalty. Drag the k1k_1 slider to see saturation kick in: at k1=0k_1 = 0 every matching document gets the same TF score regardless of count; at high k1k_1 BM25 behaves more like raw TF. Set b=0b = 0 to disable length normalization entirely.

The Real Complexity

How BM25 actually scores a document for a multi-word query q={q1,,qn}q = \{q_1, \dots, q_n\} is a sum over each query term:

BM25(D,q)=i=1nIDF(qi)f(qi,D)(k1+1)f(qi,D)+k1(1b+bDavgdl)\text{BM25}(D, q) = \sum_{i=1}^{n} \text{IDF}(q_i) \cdot \frac{f(q_i, D) \cdot (k_1 + 1)}{f(q_i, D) + k_1 \cdot \left(1 - b + b \cdot \frac{|D|}{\text{avgdl}}\right)}

where f(qi,D)f(q_i, D) is the raw frequency of term qiq_i in document DD, D|D| is document length in words, avgdl is the average document length across the corpus, and IDF(qi)=ln ⁣(Nni+0.5ni+0.5+1)\text{IDF}(q_i) = \ln\!\left(\frac{N - n_i + 0.5}{n_i + 0.5} + 1\right) down-weights terms that appear in many documents.

  • Per-query complexity is linear. Scoring one document against a qq-term query takes O(q)O(q) arithmetic operations. Ranking all NN documents takes O(Nq)O(N \cdot q) — fine for a few hundred results but impractical at web scale.
  • The inverted index makes it fast. In practice, retrieval engines store a posting list for each vocabulary term. A query only touches the (typically tiny) fraction of documents that actually contain at least one query term, so real throughput is far better than O(Nq)O(N \cdot q).
  • Tuning k1k_1 and bb. The defaults (k1=1.2k_1 = 1.2, b=0.75b = 0.75) work well across many text collections, but retrieval benchmarks show that tuning them to a specific corpus and query distribution can yield measurable gains. Some collections (e.g., very short tweets) prefer bb close to 0; others (legal documents) prefer higher bb.
  • BM25+ and BM25F. Variants address edge cases: BM25+ adds a floor to prevent zero scores for very rare terms; BM25F extends the formula to structured documents with multiple fields (title, body, anchor text), weighting each field separately.

BM25 occupies a sweet spot: it is grounded in a probabilistic model of relevance, it is simple enough to implement in an afternoon, and it scales to billions of documents when paired with an inverted index.

Where It Matters

BM25's simplicity and solid empirical performance have made it the default choice across a surprising range of systems:

  • Full-text search infrastructure. Elasticsearch, Apache Lucene, and Apache Solr all ship BM25 as the default scorer (Lucene switched from TF-IDF to BM25 in version 6.0 in 2016). Any application that uses these libraries — from e-commerce product search to log analytics — is already running BM25.
  • Academic information retrieval benchmarks. TREC, CLEF, and similar evaluation campaigns routinely use BM25 as the strong baseline that neural models must beat. Many papers still report BM25 as their strongest non-neural competitor.
  • Retrieval-Augmented Generation (RAG). Modern large-language-model pipelines often pair BM25 (fast, exact-match) with dense vector search (semantic similarity) in a hybrid retrieval step. BM25 anchors the exact-keyword leg of the hybrid.
  • Legal and biomedical search. Domains with precise terminology (case numbers, drug names, gene symbols) favor BM25's exact-match character over the fuzzier behavior of learned embeddings.
  • Question answering. Open-domain QA systems like DPR (Dense Passage Retrieval) were benchmarked against BM25 and initially struggled to beat it on keyword-heavy questions, which drove the research community toward hybrid approaches.

Understanding BM25 is a prerequisite for understanding modern retrieval — even the newest neural ranking models are evaluated relative to it, and many production systems still rely on it as their primary or fallback ranker. It connects naturally to Bayesian inference and the probabilistic relevance model that inspired it.

Conclusion

BM25 is a masterclass in applied probabilistic thinking. It starts from the observation that raw word-counting is naive — the tenth occurrence of "python" in a document tells you less than the first, and a book-length document mentioning a word once should not beat a short focused article — and fixes both problems with a single elegant formula.

Three decades on, BM25 is still the default scorer in the most widely deployed search infrastructure on the planet. Neural re-rankers have closed the gap in benchmark evaluations, but they almost always sit on top of a BM25 first-stage retriever rather than replacing it outright. The formula's combination of speed, interpretability, and solid empirical performance is simply hard to beat.

The next time a search engine surfaces exactly the document you were looking for, there is a good chance that a 30-year-old sum of logarithms — tweaked by two parameters named k1k_1 and bb — had the final say.

Share this article

Pick a channel — or use your device's native share sheet.

Comments

Loading comments...

https://www.kipuhub.com/en/article/bm25-ranking/Content licensed under CC BY-NC 4.0.