Information retrieval & BM25
Bag-of-words gave you a representation; information retrieval is the system that uses it to find the relevant documents among millions, fast. Two pieces make a search engine: an inverted index for speed, and a ranking function — BM25, the refined TF-IDF that has been the strong baseline for decades.
Written and reviewed by Shreyash Prashu Last reviewed
What you'll learn
- The inverted index — the data structure that makes search fast
- BM25 — TF saturation and length normalization over plain TF-IDF
- Why exact-term matching is BM25's blind spot (the lexical gap)
- Why modern search is hybrid — BM25 plus dense retrieval
Before you start
Bag-of-words and TF-IDF tell you how to represent a document. Information retrieval (IR) is the system that uses those representations to answer a query — find the documents most relevant to “best italian restaurant,” out of millions, in milliseconds. Two pieces make it work: an inverted index for speed, and a ranking function for relevance.
The inverted index: don’t scan, look up
Checking every document against the query is O(N) — hopeless at web scale. The trick is to invert the data. Instead of “document → its words,” build “word → the documents containing it” — a postings list per term. A query then jumps straight to the documents that contain its terms, never touching the rest.
BM25: a smarter TF-IDF for ranking
Once you have the candidate documents, you rank them. Plain TF-IDF works, but BM25 (Best Matching 25) — the long-standing default in Lucene, Elasticsearch, and friends — fixes two things TF-IDF gets wrong:
- TF saturation — ten occurrences of a word don’t make a document ten times more relevant.
BM25 lets term frequency saturate (a
k1parameter), so the benefit of repeats levels off. - Length normalization — a term in a short, focused document is more telling than the same
term buried in a long one. BM25 normalizes by document length (a
bparameter).
import math
corpus = ["the cat sat on the mat", "the dog chased the cat", "dogs and cats are common pets"]
docs = [d.split() for d in corpus]
N = len(docs); avgdl = sum(len(d) for d in docs) / N
def idf(term):
df = sum(term in d for d in docs)
return math.log(1 + (N - df + 0.5) / (df + 0.5))
def bm25(query, d, k1=1.5, b=0.75):
s = 0.0
for term in query:
tf = d.count(term)
if tf:
s += idf(term) * (tf * (k1 + 1)) / (tf + k1 * (1 - b + b * len(d) / avgdl))
return s
for i in sorted(range(N), key=lambda i: bm25(["cat"], docs[i]), reverse=True):
print(f"doc{i} score={bm25(['cat'], docs[i]):.3f} {corpus[i]!r}")
doc1 score=0.496 'the dog chased the cat'
doc0 score=0.458 'the cat sat on the mat'
doc2 score=0.000 'dogs and cats are common pets'
Two things to notice. doc1 outranks doc0 even though both contain cat exactly once — because
doc1 is shorter, so length normalization gives its match more weight. And doc2 scores
zero: it contains cats, not cat, and BM25 only matches exact terms.
The lexical gap — and hybrid search
That zero is the whole limitation. BM25 (like all bag-of-words methods) matches surface terms,
so it misses morphology (cat vs cats — which is why you stem
first) and is blind to meaning (car vs automobile, a query and a paraphrase). It can’t
retrieve a document that’s clearly relevant but shares no exact words.
In one breath
- Information retrieval finds relevant documents for a query at scale with two pieces: an inverted index (speed) and a ranking function (relevance).
- The inverted index maps term → documents containing it, turning search from an O(N) scan into a direct lookup of the query terms’ postings.
- BM25 refines TF-IDF for ranking with TF saturation (repeats give diminishing returns,
k1) and length normalization (terms in short docs count more,b) — the demo’s shorterdoc1outranksdoc0. - BM25 matches exact terms only — the lexical gap: it misses morphology (
cat/cats, scoringdoc2zero) and meaning (synonyms), so stem first and don’t expect semantics. - Modern search is hybrid: BM25 (exact/rare terms) + dense retrieval (meaning), scores fused — a decades-old baseline that’s still essential.
Quick check
Quick check
Next
BM25 is the lexical half of retrieval; the semantic half is dense
embeddings and RAG. The morphology gap that zeroed
doc2 is why you preprocess and stem first.
Practice this in an interview
All questionsHybrid search combines dense vector similarity with sparse keyword search such as BM25, then fuses their rankings. Dense retrieval captures semantic meaning while keyword search preserves exact terms, identifiers, and rare tokens, so the combination often improves retrieval quality when a corpus contains both natural-language questions and precision-critical strings.
Keyword search (BM25) excels at exact term matching — product codes, proper nouns, rare abbreviations. Semantic search (dense embeddings) captures meaning and handles paraphrases. Hybrid search runs both in parallel and merges scores with Reciprocal Rank Fusion, giving the best of both worlds for most production RAG systems.
TF-IDF combines a term's frequency within a document with inverse document frequency across the corpus, giving high weights to terms that are locally important but globally distinctive. Compared with raw bag-of-words counts, it reduces the influence of common words and usually produces better features for retrieval and classical text classification.
No single method wins: use PageIndex when a small set of well-structured documents makes section and page hierarchy important, and use a vector database when you need fast retrieval across a large or messy corpus. PageIndex trades embedding lookup for LLM-guided tree navigation, so a hybrid system often uses vectors to shortlist documents and PageIndex to find evidence inside one.