Sam Austin AI

Vector Database Indexing Explained: HNSW vs IVF vs Flat

September 25, 2026 13 min read Sam Austin
Contents

You clicked on this article because you typed "vector database indexing" into Google and felt a sudden urge to understand what's going on under the hood. Good call. Maybe you're building a semantic search feature, or your RAG pipeline is returning results that feel... spiritually related to your query rather than actually related. Either way, you're in the right place.

I've spent more hours than I'd like to admit tuning vector indexes, and I can tell you this upfront: the index you choose matters more than the embedding model you picked, after a certain point. Fight me in the comments. Let's walk through the three big players — Flat, IVF, and HNSW — like we're grabbing coffee and complaining about query latency together.

By the end of this guide you'll know exactly what each index does under the hood, which one fits your dataset size and update pattern, and how to tune the three or four parameters that actually move recall and latency. IMO, "always benchmark against a Flat baseline" is the single habit that separates engineers who understand their recall numbers from engineers who just hope they're fine :)

Vector Database Indexing HNSW vs IVF vs Flat ANN Search Recall Tuning

Figure 1: Vector indexing — a navigable map over your embeddings instead of reading the whole library

Image Alt Text: "Vector database indexing explained: HNSW vs IVF vs Flat approximate nearest neighbor search"

Why Vector Indexing Even Exists

Here's the problem in one sentence: comparing your query against a million embeddings is slow. Painfully, why-is-my-coffee-cold slow.

A vector index exists to avoid that brute-force misery. Without one, your database computes the distance between your query vector and every single vector in the collection. With one, the database shrinks the candidate pool so it only checks the vectors that actually matter.

Do the arithmetic once and this stops being abstract: a million 768-dimension vectors means roughly 768 million multiply-accumulate operations per query before you've even sorted the results. Multiply by your queries-per-second target and brute force stops being an option somewhere around the point your cloud bill develops opinions about your architecture. Recall the caching article's point that vector search cost scales with dimension count — every extra dimension makes the exact scan proportionally more expensive, which is why index choice shows up in both your latency graph and your invoice.

Think of it like finding a book. Flat search means reading every book in the library to find the one you want; an index means the librarian already knows which shelf it's on. Simple enough, right? Now let's see which librarian you're actually hiring.

Flat Index: The "Brute Force and Chill" Approach

The Flat index — sometimes called flat or brute-force index — does exactly what it says on the tin. It compares your query against everything. Every vector. No shortcuts, no cleverness.

Why Would Anyone Use This?

Honestly? Accuracy. The Flat index is the ground truth of the vector world. Because it checks everything, it never misses. It gives you 100% recall, which means the true nearest neighbors always show up in your results.

I use Flat indexes constantly during development and benchmarking. When I evaluate whether HNSW or IVF is "good enough" for a project, I benchmark against Flat results — it's my reference point, the control group of vector search. This is also exactly the discipline the FAISS tutorial pushed earlier in this series: start with IndexFlatL2, confirm your pipeline is correct, and only add approximation once you actually need it. Recall the Redis article making the same point about flat indexes being how you measure how much recall your HNSW configuration is sacrificing for speed.

import faiss
import numpy as np

dim = 768
xb = np.random.rand(200_000, dim).astype("float32")

flat = faiss.IndexFlatL2(dim)
flat.add(xb)

xq = np.random.rand(5, dim).astype("float32")
distances, labels = flat.search(xq, 10)

Five lines, no training step, no parameters to tune, and every neighbor returned is genuinely the closest one. That last property is the whole reason Flat survives in a world full of clever approximate algorithms.

  • Recall: Perfect. It's checking everything.
  • Speed: Slow. It scales linearly with your dataset size.
  • Memory: Low. No fancy graph structures eating your RAM.
  • Best for: Small datasets (under ~100k vectors), benchmarking, and debugging your pipeline.

If your dataset fits comfortably in memory and queries-per-second are low, Flat is honestly fine. Don't over-engineer a problem you don't have.

IVF Index: Divide and Conquer (With a Catch)

IVF stands for Inverted File Index, which sounds like something from a spy novel but is really just clustering. The idea is straightforward: before you ever run a query, the database groups your vectors into clusters using an algorithm like k-means.

How It Actually Works

When a query comes in, IVF doesn't scan the whole dataset. It finds the few clusters closest to your query and searches only within those. If you have a million vectors split into 1,000 clusters, you might only search 1%-5% of your data. That's a massive speedup.

Here's the catch, and it's a big one: IVF can miss results. If your query vector lands near a cluster boundary, the true nearest neighbor might live in a neighboring cluster you didn't check. Oops.

You can fix this by searching more clusters — a parameter called nprobe — but that trades speed for recall. Crank it high enough and congratulations, you've reinvented a slower Flat index.

nlist = 1024  # clusters to build
quantizer = faiss.IndexFlatL2(dim)
ivf = faiss.IndexIVFFlat(quantizer, dim, nlist, faiss.METRIC_L2)

ivf.train(xb)      # IVF must see data before it can cluster it
ivf.add(xb)

ivf.nprobe = 20    # search 20 of 1,024 clusters per query

Notice the train() call that Flat never needed — IVF has a build phase, which means index quality depends on the sample you trained it on. Feed it a skewed training set and your clusters reflect that skew forever, until you retrain. pgvector exposes exactly this family as ivfflat, and the pgvector tutorial in this series positions it as the faster-to-build alternative to HNSW.

My Honest Take on IVF

I've deployed IVF for large, relatively static datasets where query volume was high and I needed to keep memory usage reasonable. It did the job. But tuning nprobe felt like adjusting a shower knob that's either ice or lava — the sweet spot exists, but finding it takes patience.

  • Recall: Good, but not guaranteed. Depends heavily on nprobe.
  • Speed: Fast for large datasets, especially with a GPU.
  • Memory: Lower than HNSW.
  • Best for: Huge static datasets, high query throughput, memory-constrained setups.

Worth knowing directly: the IVF family is where product quantization joins the party — IVFPQ clusters your vectors and compresses them inside each cluster, cutting memory further at the cost of another notch on the recall dial. That's a genuinely different lever from the quantization article's model-weight compression, even though the motivation — buy speed and memory with accuracy — is identical.

HNSW: The Graph That Broke the Internet

Alright, the headliner. HNSW — Hierarchical Navigable Small World — is the most popular approximate nearest neighbor algorithm in vector databases right now, and for good reason. Qdrant, Weaviate, Milvus, Elasticsearch, pgvector — they all lean on it heavily.

The Intuition (No Math Degree Required)

Imagine you're looking for a coffee shop in a city. You don't check every building on every street. You start from a landmark, walk toward the neighborhood, then the block, then the exact spot. HNSW builds exactly that kind of layered map over your vectors.

The index creates a multi-level graph. The top levels are sparse and cover long distances. The bottom level contains all your vectors with dense local connections. A query zooms through the layers like a person spiraling down toward the target — long hops at the top, short hops at the bottom, until you're standing in the right coffee shop wondering why you didn't just stay home and drink instant.

hnsw = faiss.IndexHNSWFlat(dim, 32)          # 32 = M, links per node
hnsw.hnsw.efConstruction = 200               # build-time candidate list
hnsw.add(xb)

hnsw.hnsw.efSearch = 64                      # query-time candidate list
distances, labels = hnsw.search(xq, 10)

Three parameters, and only one of them (efSearch) touches your query-time latency. That's the entire tuning surface for most projects.

Why Everyone Loves It

The results speak for themselves. HNSW delivers excellent recall at high speed, and it handles dynamic data well — you can insert vectors without rebuilding the entire index, which is a dealbreaker-or-not feature for real-time applications. FYI, deletes are the quieter caveat: most implementations tombstone removed vectors rather than rewiring the graph, so a heavily-deleted index can still pay memory for ghosts until you compact it.

From my own experience, HNSW hits recall above 0.95 with default parameters on most datasets. I've had to tune it maybe twice in two years of regular use — it's the closest thing to "set it and forget it" this field offers. :) That matches what the vector database comparison found across vendors: recall on HNSW-based engines converges in the 95-99% range regardless of whose logo is on the box, which is a useful reminder that the index family matters more than the brand.

  • Recall: Excellent, typically 0.95+ with sane parameters.
  • Speed: Very fast, even on big datasets.
  • Memory: The tradeoff — graphs are RAM-hungry.
  • Best for: General-purpose production workloads, real-time apps, dynamic datasets.

pgvector's recommended default in this series' own tutorial is m = 16, ef_construction = 64; Elasticsearch builds the same style of graph and even offers GPU-accelerated graph construction, because the expensive part of HNSW is building it, not querying it.

HNSW vs IVF vs Flat: The Head-to-Head

Let's put them side by side, because that's what you actually came here for.

Criteria Flat IVF HNSW
Recall 100% Good Excellent
Query speed Slow Fast Fastest
Build time Instant Medium Slow
Memory usage Low Medium High
Insertions/deletions Trivial Needs care Handles well
Tuning difficulty None Moderate Low

So which one wins? Depends on your situation, obviously. Anyone who gives you a universal answer is selling something.

  • Small dataset, low traffic? Flat. Stop overthinking it.
  • Massive static dataset, tight memory? IVF deserves a look.
  • Production system with real users and changing data? HNSW, nine times out of ten.

The Tuning Cheat Sheet

If you do end up tuning — and you will — these are the dials worth touching. Everything else is mostly defaults behaving themselves:

Index Parameter What it controls Sensible starting point
IVF nlist Clusters created at build time Roughly 4 * sqrt(N) vectors
IVF nprobe Clusters searched per query 1%-5% of nlist, then tune
HNSW M Neighbor links per node 16-32 (higher = more RAM, better recall)
HNSW ef_construction Candidate list while building 100-200 (slower build, better graph)
HNSW ef / efSearch Candidate list while querying 64-128, raise it for recall

One rule ties all of these together: every parameter that raises recall also raises latency or memory, and every parameter that lowers them also lowers your recall floor. There is no free lunch in this table, only invoices paid in different currencies.

And don't forget the filter interaction, because it's the one production surprise nobody warns you about. Recall the Elasticsearch tutorial's pre-filtering discussion directly: filters applied before the graph traversal shrink the candidate pool and speed things up, while filters applied afterwards force the index to over-fetch and then throw results away. The same tension shows up in IVF (fewer clusters are valid, so effective nprobe drops) and in HNSW (a tight filter can make a graph hop land on nothing).

Common Mistakes I See People Make

Let me save you some pain. These are mistakes I've made myself so you don't have to.

  • Benchmarking against nothing. Always compare your approximate index against a Flat baseline. Otherwise you're flying blind on recall — you have no way to know whether your nprobe = 5 setting is a smart optimization or a silent quality regression.
  • Ignoring the metric. Cosine, Euclidean, dot product — the index choice interacts with your distance metric. Pick deliberately: FAISS needs METRIC_INNER_PRODUCT (or L2-normalized vectors) for cosine similarity, and the embedding models comparison warned about the same normalization discipline on the model side.
  • Default parameters forever. HNSW's defaults are good, but ef and M exist for a reason. Spend an afternoon tuning them. IMO, it's the highest-ROI afternoon you'll spend all quarter.
  • Chasing 100% recall in production. You probably don't need it. A 2% recall drop for 10x speed is almost always the right trade — and if you don't believe that, ask what your users actually notice: a missing fifth-best result, or a spinner.
  • Picking the index before measuring the dataset. Recall the FAISS tutorial's honest take: at a few hundred thousand vectors, IndexFlatL2 is usually fast enough and always correct. Index selection is a scaling decision, not a starting config.
  • An Introduction to Information Retrieval by Christopher, Manning & Raghavan — the foundational text on inverted indexes and ranked retrieval; IVF is literally an inverted file index, so this is the theory sitting underneath half the parameters in this article.
  • AI-Powered Search by Trey Grainger et al. — modern search engineering covering when vector retrieval belongs in a ranking pipeline and how it composes with keyword and semantic signals.
  • Designing Machine Learning Systems by Chip Huyen — retrieval system design and the serving-side discipline that decides which index tradeoffs actually matter once real traffic arrives.

Unlock AI That Actually Works

Get lifetime access to GPT-6 Astra, Claude Fable 5.1, Gemini 3.5, Grok 4.5, and more — all in one platform. Build websites, apps, videos, content, and digital products from a single command. No monthly fees. No tool-hopping.

Click here to get GPTAstra Max now — one-time payment, lifetime access.

Frequently Asked Questions

What does a vector database index do?

It shrinks the candidate pool so the database doesn't compare your query vector against every stored vector. An exact (Flat) index guarantees correct neighbors but scales linearly with data size; approximate indexes like IVF and HNSW trade a small amount of recall for large speed and memory gains.

What is the difference between HNSW, IVF, and Flat?

Flat brute-forces every vector: 100% recall, linear cost, lowest memory. IVF clusters vectors with k-means and searches only the nearest clusters, controlled by nprobe: fast and memory-light, but it can miss neighbors near cluster boundaries. HNSW builds a hierarchical proximity graph and navigates it: fastest queries and excellent recall, at the cost of the highest memory footprint.

When should I use a Flat (brute-force) index?

For datasets under roughly 100k vectors, for low query throughput where latency is already fine, and always as the ground-truth baseline when you benchmark an approximate index. Flat results are the control group you measure HNSW or IVF recall against.

What is nprobe in an IVF index?

nprobe is how many of the clusters created at build time get searched per query. Raising it increases recall and latency together — high enough and IVF converges on a slower Flat scan. A common starting point is searching around 1-5% of the cluster count, then tuning against a Flat baseline.

Which parameters tune an HNSW index?

M controls how many neighbors each node links to (higher means better recall and more memory), ef_construction controls the candidate list used while building (higher means slower build, better graph), and ef or ef_search controls how many candidates are kept during a query — the main recall-versus-latency dial at search time.

Which vector index gives 100% recall?

Only Flat — it checks every vector, so true nearest neighbors always appear. Approximate indexes such as IVF and HNSW can miss neighbors by design; the practical question is how much recall you're buying back for the speed, which you can only answer by measuring against a Flat baseline.

Wrapping It Up

Here's the short version, since you've earned it: Flat is your truth, IVF is your memory-saver, and HNSW is your workhorse. Pick Flat for small data and benchmarks, IVF for massive static collections, and HNSW for almost everything else.

The best part? Most modern vector databases let you switch indexes without much drama — pgvector offers ivfflat and hnsw on the same column, FAISS hands you all three in the same library, and the vector database comparison showed how much of this decision is really about the operational wrapper rather than the algorithm underneath. So build your baseline with Flat, measure, then upgrade. Your future self — the one who isn't staring at a latency dashboard at 2 a.m. — will thank you.

Remember that recall is only ever meaningful against a baseline, that every tuning knob in this article trades speed for accuracy in one direction or the other, and that the index you choose starts mattering more than the embedding model you chose only once your dataset is big enough for it to. FYI, this article closes a loop the FAISS tutorial opened earlier in this series — that guide introduced the three index types in passing, and this is the full argument for when each one earns its place in your stack :)

Now go index something. And if your semantic search still returns soup recipes for "database sharding tutorials," well... that's a different article. :)

Share this article X Facebook LinkedIn Reddit WhatsApp

Related Articles