Heads up: posts on this site are drafted by Claude and fact-checked by Codex. Both can still get things wrong — read with care and verify anything load-bearing before relying on it.
why → how

Why vector search is approximate on purpose

Exact nearest-neighbor search exists, works, and is correct. At scale, the AI-era retrieval stack quietly walks away from it. The reason is more interesting than 'it's faster.'

AI & ML intermediate Apr 29, 2026 · updated Aug 25, 2026 · 14 min read

On this page

The picture version

Six pictures for a reader who has never heard of a vector database. The prose below fills in the seams the pictures skip.

1 · The thing that should make you squint

A search box that admits, in its own documentation, that it might be wrong.

an ordinary database “give me row 42” row 42. always. search over 5,000,000 help articles “how do I cancel my subscription?” the closest ten. probably. Everywhere else in software, “probably” is a bug report. here it is the setting almost every large deployment turns on, deliberately
Vector databases can do exact search — it’s the default when you build no index at all. Large deployments mostly walk away from it on purpose, and the reason is more interesting than cost.

2 · Why exact stops paying

The trick that makes exact search fast stops working in many dimensions.

on a flat map the search touches one small square — everything else can be skipped without looking in the space embeddings actually live in the search region reaches into nearly every division, so you end up walking almost the whole corpus anyway Exact search doesn’t become wrong. It becomes a full scan wearing an index.
In two or three dimensions, dividing the space lets a search ignore most of it. In the hundreds of dimensions embeddings use, distances bunch up and the divisions stop pruning — the honest version is that exact search often approaches brute force, not that it always collapses to it.

3 · The thing nobody mentions

The “correct” ranking you’d be protecting was never sharp.

what the embedding model is actually sure about 1   “Cancelling your plan” confident 4   “Managing your billing cycle” 5   “Changing your payment method” 6   “Refunds and credits” 7   “Pausing your account” order is a coin toss 500   “Keyboard shortcuts” clearly not it Retrain the model with a different random seed and the middle re-shuffles. The top and bottom don’t.
An embedding model learns which directions are close, not an exact distance. Its own ranking wobbles at the margins — it is sure that a cancellation article beats a keyboard-shortcuts article, and genuinely unsure which of four billing articles deserves the top slot.

4 · The argument, in one picture

The index’s mistakes are smaller than the mistakes already in the question.

how far the embedding is from what the reader actually wanted this gap is already there before any index exists how far the index strays from the exact answer “swap result 5 with result 6”, mostly Spending a lot of compute to close the small gap does not move the answer the reader sees.
Drawn to the same scale, the index’s error sits inside the embedding’s error. That is why the last few points of accuracy are so often not worth paying for — a claim about this regime, noisy human-written text, not a law of nature.

5 · How the skipping is done

Three ways to avoid looking at five million things, each with the same dial.

walk a graph each hop lands closer to the query; most of the corpus is never visited open a few drawers billing score the drawers nearest the query, ignore the rest hash into buckets the query’s bucket a hash designed so similar things land in the same bucket; score only those All three carry one knob: look at more, miss less, go slower. turning it all the way up just gets you the full scan back
Graph walks, clustered cells, and hash buckets cheat in different directions, but they all expose the same dial. Knowing why the search is approximate is what tells you where to leave that dial — otherwise you tune it by guessing.

6 · Keep this card

The whole thing on one index card.

approximate search = an index that skips most of the data + a small accepted miss rate — because the question was already fuzzy so when retrieval is bad, suspect the embeddings and the chunking first — that is usually the bigger error unless a miss flips a yes/no answer: for duplicate detection or copyright matching, the argument stops holding
Picture to keep: a ruler marked in millimetres measuring something whose edges are blurred by a centimetre — grinding finer marks tells you nothing new about where the edge is. The index is rounded to the precision of the question it’s being asked. Change to a domain with a cleaner signal and that ordering flips.

Why it exists

You’re building the thing everyone builds now: a search box over your company’s help center, five million chunks of documentation, where typing “how do I cancel my subscription” should surface the right article even though it never uses the word “cancel.” That’s the running example for the rest of this post. You reach for a vector database — Pinecone, Qdrant, Weaviate, pgvector, FAISS — and in the docs there’s a phrase that should make you stop and squint: approximate nearest neighbor. Approximate. The system whose entire job is “find me the most similar vector to this query” is openly telling you it might not return the actual most similar vector.

You probably read that as a compromise the vendor is apologizing for — “we’d give you the exact answer if only we could afford it.” It’s closer to the opposite. These systems do support exact search — it’s pgvector’s default when you build no index at all, and it’s FAISS’s IndexFlat, Qdrant’s exact mode, Weaviate’s flat index. They’re just not what most production deployments switch on once the corpus gets big. Approximate becomes the default, on purpose, and the reason isn’t only cost.

That should still feel strange. We don’t accept it from databases elsewhere. A SQL WHERE id = 42 that returned “probably row 42, but maybe row 39, sorry” would be a bug. Why is the AI-era retrieval stack happy to ship the equivalent?

There are two answers, and both are worth carrying around. The boring one is that exact NN search degrades sharply in high dimensions: the data structures that make exact search fast in low-dimensional space lose most of their pruning power, and you end up not far from a linear scan over the whole corpus. The interesting one is empirical: in the workloads where approximate nearest neighbour (ANN) indexes dominate, the embeddings themselves are noisy enough that the index’s near-misses are roughly the same size as the embedding model’s own ranking jitter. So an “exact” answer to a fuzzy question is no better than an approximate answer to it. That’s a regime claim, not a law — true for text/RAG, less true for tasks with cleaner signals — and it’s the one worth understanding.

Why it matters now

If you’re building anything on RAG or semantic search, ANN is the layer your latency, recall, and bill all sit on top of. Treating it as a black box leads to predictable disappointments:

The mental model “vector DB = approximate on purpose, and that’s fine because embeddings are fuzzy” answers most of those.

The short answer

ANN = sub-linear-time index + accept a small recall loss + big speedup, justified because the embedding distance is itself fuzzy

Picture to keep: the index is a ruler marked in millimetres being used to measure something whose edges are blurred by a centimetre. Grinding finer marks into the ruler doesn’t tell you anything new about where the edge is.

Exact nearest-neighbor in high dimensions tends to collapse toward linear scan — the indexes that prune well in 2D or 3D lose most of their power. ANN indexes (graph-based like HNSW, partition-based like IVF, hash-based like LSH) skip most of the data using clever structure, at the cost of occasionally missing the true top-k. In text/RAG-style workloads the cost is usually acceptable because the embedding model’s own ranking is already noisy at the margins.

How it works

Start from the naive plan and let it break twice.

Naive attempt: for each query, compute the distance to all five million help-center chunks and keep the closest ten. This is correct by construction, and for a small corpus it’s the right answer — stop here.

1. Why it breaks: exact NN at scale gets ugly fast

There’s a classical result loosely called the curse of dimensionality. The compact statement: under many distributions, as dimensionality grows the distances between points concentrate, and partitioning structures that pruned aggressively in low dimensions lose most of their leverage. A k-d tree splits space at each node, but in high-dimensional spaces a query’s “nearby” region touches almost every branch, so the tree ends up walking most of the data anyway. Beyer et al. (1999), “When Is ‘Nearest Neighbor’ Meaningful?”, is the standard reference for the concentration effect itself, under stated conditions — the consequence for indexes is the usual practical reading of it, not something the paper proves outright. So the honest version is often approaches brute force, not always collapses to it.

That leaves you with mostly-honest options for big high-dimensional corpora:

Neither comfortably hits the low-millisecond, large-corpus, single-node bar that production semantic search is usually held to. So the field went looking for something that isn’t exact — which sounds like a straightforward accuracy-for-speed trade, until you look at what “accuracy” means here.

2. The fix is cheaper than it looks: approximation is often effectively free in embedding space

This is the part that’s worth internalizing, with the caveat that it’s a regime claim, not a theorem. It’s true for the workloads that drove ANN to dominance (text-style RAG, semantic search over noisy human-written corpora). It’s less true for tasks with cleaner signals, which I’ll come back to.

An embedding model maps text (or images, or whatever) into a vector. The training objective shapes which directions are close, but it does not nail down a precise distance: two equally-good paraphrases of a sentence land in nearby-but-not-identical points, and the model’s “rank” of the top results for a query is genuinely noisy near the boundary. Re-train the model with a different seed, or even use a different pooling strategy, and the ordering of result #4 vs. result #7 will jiggle. Result #1 vs. result #500 won’t. In help-center terms: the model is confident that “Cancelling your plan” beats “Keyboard shortcuts” for your query, and genuinely unsure whether “Cancelling your plan” or “Managing your billing cycle” should be #1.

Now layer an ANN index on top. A well-tuned HNSW index typically returns the true top-k most of the time, and when it misses, it usually returns vectors that are almost as close as the true neighbor — its errors look like “swap result #5 with result #6,” not “completely miss the topic.” That error is roughly the same kind, and the same scale, as the embedding’s own ranking noise.

Stack them and you get the argument: in this regime, the ANN-vs-exact gap lives below the embedding-vs-truth gap. So spending compute to close the ANN gap doesn’t move end-to-end retrieval quality much. You’re sharpening a measurement whose underlying signal was already blurry.

This is why “high recall” benchmarks for ANN are easy to over-index on. The last few percentage points of recall often cost a sizable multiple in latency, and in an end-to-end RAG eval the difference is frequently inside the noise of the LLM’s own answer variability. (There is no canonical citation for this — it’s the folklore claim that “embedding error dominates ANN error” in noisy text workloads. The shape is well-supported by ANN benchmarks like ann-benchmarks.com, but how universally it holds across embedding models and corpora is a claim that varies by setup.)

How the indexes actually skip work

Three families dominate, and each cheats in a different direction. Keep the same query in mind — the vector for “how do I cancel my subscription” arriving at five million chunks:

All three give you sub-linear query time and a knob that says “trade more compute for fewer mistakes.” The memory bill varies by family — graph indexes like HNSW typically end up larger than the raw vectors; IVF combined with PQ is often smaller, sometimes by an order of magnitude. (The FAISS index guide gives per-index bytes-per-vector numbers if you want to size this for your own corpus rather than trust the shape of the claim.)

Where the argument breaks down

The “approximation is free” story is contingent. A few honest exceptions:

You started with ANN = sub-linear index + accept a small recall loss. What did this post add? — + a noise floor you didn't put there, and it’s the half that changes what you should optimize. Vector databases are approximate not because the field gave up on correctness, but because correctness is being measured against a ranking that was already approximate before the index ever saw it. The index is rounded to the precision of the question it’s being asked.

Which answers the squint you started with: the docs aren’t apologizing. For your help-center search, “the exact 10th-nearest chunk” isn’t a fact the system is failing to deliver — it’s a number the embedding model was never confident about in the first place. In most text/RAG setups, what you should worry about first is whether the embeddings and the chunking are any good, because that’s the error term that usually dominates. Move to a domain with a cleaner signal and that ordering can flip.

Check yourself

Before you go — your RAG answers are bad, so you raise HNSW’s efSearch until measured recall@10 goes from 92% to 99.5%. Answer quality doesn’t budge. What does that tell you?

Answer

That the index was never your bottleneck. Recall@10 is measured against the exact nearest neighbours in embedding space, so 92% → 99.5% means you closed the ANN-vs-exact gap — and the fact that nothing downstream changed is direct evidence that the embedding-vs-relevance gap is bigger. Go look at chunking, at the embedding model’s fit for your domain, or at whether the retrieved passage is even the kind of thing that answers the question. This is the post’s argument arriving as a measurement instead of an assertion: you sharpened the ruler and the edge stayed blurry.

And a trade-off: same 5-million-chunk corpus, but now the job is detecting whether an uploaded document is a near-duplicate of one already in the system. Same vectors, same index. Does the “approximation is free” argument still hold?

Answer

No, and for a reason worth naming precisely: the argument depends on a miss being cheap. In search, missing the true #1 and returning the true #2 gives the user a slightly worse but still plausible article. In dedup, the question is binary — is there a near-identical document above threshold? — so a single missed neighbour flips the answer from “duplicate” to “new,” which is a correctness bug rather than a quality wobble. The standard pattern is to keep ANN as a fast candidate generator and then exact-rerank the shortlist, which recovers the guarantee at a cost proportional to the shortlist rather than the corpus.

Going deeper