Skip to content
TheAgent Ecosystem
RAG & Knowledge

HNSW vs IVFFlat: Choosing and Tuning Your pgvector Index

The two pgvector index types, the exact params that trade recall for speed, and how to tune them.

Muhammad Qasim HammadAI-assisted7 min read1,355 words

AI-drafted, reviewed by Muhammad Qasim Hammad on July 21, 2026. See our AI disclosure.

pgvector Indexing: HNSW vs IVFFlat in pgvector
Table of contents
  1. HNSW vs IVFFlat: which pgvector index should you pick?
  2. How does HNSW work, and what tunes it?
  3. How does IVFFlat work, and why build it after loading?
  4. Which distance operator, and does it have to match?
  5. How do you tune and verify your chosen index?
  6. So which index wins for your workload?

You added the vector column, you loaded your embeddings, and your similarity query works. Then the table grows and the same query crawls. In pgvector the fix is an index, and you have exactly two real choices whose trade-offs almost every tutorial skips.

HNSW vs IVFFlat: which pgvector index should you pick?#

The choice between HNSW and IVFFlat comes down to build cost versus query performance. HNSW is a graph index with the best speed-recall tradeoff, but it builds slower and uses more memory. IVFFlat clusters your vectors, builds faster, and uses less memory, yet you must create it after the data is loaded.

Both are approximate, so both give up a little recall for speed compared to an exact scan. The pgvector README is blunt about the headline difference: HNSW "has better query performance than IVFFlat (in terms of speed-recall tradeoff), but has slower build times and uses more memory." That one sentence decides most cases, but the tuning underneath it is where posts go quiet.

Comparison of HNSW and IVFFlat pgvector indexes on build time, memory, recall, query speed, and when to use eachHNSW wins the speed-recall tradeoff; IVFFlat builds faster and uses less memory. Choose by your data shape and build budget.

Keep the shape of each index in mind. HNSW links every vector to a handful of neighbors across layers, so a search walks the graph toward the closest matches. IVFFlat splits the space into clusters and only scans the few nearest ones. The knobs you tune, and the failure modes, follow directly from those two structures.

How does HNSW work, and what tunes it?#

HNSW builds a layered graph where each node connects to its nearest neighbors, and a query hops through that graph toward the closest vectors. Two build parameters shape the graph, and one query parameter trades recall for speed at search time. You do not need to know your row count up front to build it.

At build time, m sets the max connections per layer and defaults to 16, while ef_construction sets the candidate-list size while building and defaults to 64. A larger m or ef_construction builds a richer graph with better recall, at the cost of build time and memory. At query time, hnsw.ef_search defaults to 40 and controls how wide the search looks; you raise it per session with SET hnsw.ef_search = 100 when you want more recall.

Five steps to create and tune a pgvector index: pick operator, create index, set query params, measure recall, re-tuneThe loop is the same for both index types: create, set the query knob, measure recall against exact search, then re-tune.

That last knob is the one you reach for most. Because ef_search is a session setting, you can dial recall up for a critical query path and leave it low elsewhere, without rebuilding anything. The build parameters are fixed at index creation, so getting m roughly right the first time saves you a rebuild later.

How does IVFFlat work, and why build it after loading?#

IVFFlat partitions your vectors into lists clusters during the build, then at query time it probes only the nearest few clusters instead of every row. Because the clusters are learned from the data itself, an IVFFlat index built on an empty or tiny table learns bad clusters, so you must create it after the table already holds representative rows.

The pgvector README gives a starting point for lists: use rows / 1000 for up to 1M rows, and sqrt(rows) above that. At query time, ivfflat.probes defaults to 1, and raising it scans more clusters for better recall at the cost of speed. So IVFFlat has one build knob (lists) and one query knob (probes), which makes it simpler to reason about than HNSW, if you can pay the rebuild-on-empty tax.

The catch is real churn. If your data changes constantly, the clusters drift and recall slips until you rebuild. That is exactly why the same README steers dynamic workloads toward HNSW, whose graph tolerates inserts far better without a full rebuild.

Which distance operator, and does it have to match?#

Both indexes support the same three distance operators, and the operator you query with must match the operator class you built the index for. Cosine distance uses <=>, L2 (Euclidean) uses <->, and inner product uses <#>. Build a cosine index but query with L2 and the planner silently skips the index.

That silent skip is a classic pgvector footgun. Your query still returns rows, so nothing looks broken, but you have quietly fallen back to a slow exact scan on every row. Pick the operator that matches how your embedding model measures similarity, then build the matching operator class (vector_cosine_ops, vector_l2_ops, or vector_ip_ops) so the index actually gets used.

Default pgvector parameters: HNSW m is 16, ef_construction is 64, ef_search is 40, IVFFlat probes is 1The defaults from the pgvector README. Raise the query-time knobs (ef_search, probes) to lift recall at the cost of speed.

Here is the same information as a cheat-sheet you can keep next to your migration file:

ConcernHNSWIVFFlat
StructureLayered neighbor graphClustered lists
Build paramsm (16), ef_construction (64)lists (rows/1000 up to 1M)
Query paramhnsw.ef_search (40)ivfflat.probes (1)
Build before data?YesNo, load data first
Best forDynamic data, top speedStatic data, lean memory

How do you tune and verify your chosen index?#

Tune by moving one query-time knob and measuring recall against exact search. Raise hnsw.ef_search or ivfflat.probes for more recall, lower it for more speed. To measure recall, run the query with the index and against an exact scan, then compare how many of the same IDs come back.

Decision flowchart for choosing between HNSW, IVFFlat, and no index in pgvector based on data size, build budget, and data churnStart from table size and build budget; every path ends at measure recall against an exact scan before you trust it.

Recall is not a vibe; it is the overlap between your approximate results and the true nearest neighbors from an exact scan. If your index returns 8 of the true top 10, that is 0.8 recall, or 80%. Decide the recall you actually need for the task, tune until you hit it, and only then chase latency. If you are still choosing a vector store at all, our comparison of pgvector against Chroma and Qdrant covers when Postgres is the right home for these indexes in the first place.

So which index wins for your workload?#

Neither index wins outright; the right pick is the one that matches your data and build budget. Choose HNSW for the best query performance and for data that changes often, since its graph tolerates inserts. Choose IVFFlat when build time and memory are tight and the data is mostly static, and remember to build it after loading.

Whichever you pick, treat the defaults as a starting line, not a finish. Build the index, set one query-time knob, measure recall against an exact scan, and re-tune until both recall and latency sit where the task needs them. Verify the current parameter names against the pgvector README before you standardize a migration, because this project moves fast.

Frequently asked questions

What is the difference between HNSW and IVFFlat in pgvector?
HNSW is a graph index that links each vector to its neighbors across layers, giving the best speed-recall tradeoff at the cost of slower builds and more memory. IVFFlat clusters vectors into lists and scans only the nearest few at query time, so it builds faster and uses less memory but generally trades some recall. HNSW suits dynamic data; IVFFlat suits static data where build cost matters.
Do I need an index at all in pgvector?
No. Without any index, pgvector performs exact nearest-neighbor search, which gives perfect recall by scanning every row. That is fine for small tables, but it gets slow as rows grow. You add an HNSW or IVFFlat index precisely to trade a small amount of recall for much faster approximate search once exact scans become the bottleneck.
What are the default HNSW parameters in pgvector?
Per the pgvector README, HNSW builds with m at 16 (the max connections per layer) and ef_construction at 64 (the candidate-list size while building the graph). At query time, hnsw.ef_search defaults to 40 and controls the candidate-list size during search. Raising ef_search improves recall at the cost of speed, and you set it per session with SET hnsw.ef_search.
How do I set the lists parameter for IVFFlat?
The pgvector README suggests lists = rows / 1000 for up to 1M rows, and sqrt(rows) above 1M rows, as a starting point. Because IVFFlat learns its clusters from the data, you must create the index after the table already has representative rows. At query time you raise ivfflat.probes (1 by default) to scan more lists and lift recall.
Does the distance operator have to match the index?
Yes. pgvector indexes are built for a specific distance, so the operator class you choose must match the operator you query with. Cosine uses <=>, L2 uses <->, and inner product uses <#>. If you build a cosine index but query with L2, the planner will not use the index, and you fall back to a slow exact scan without realizing it.

Sources

Primary references and vendor documentation used while drafting and reviewing this article.

  1. pgvector README (HNSW and IVFFlat indexing, parameters, distance operators, exact search)
  2. pgvector HNSW index options (m, ef_construction, hnsw.ef_search)
  3. pgvector IVFFlat index options (lists, ivfflat.probes, build after data)

Written by

Muhammad Qasim Hammad
Muhammad Qasim Hammad
AI agents & automationFounder · Cart Gaze LLCPMP-certified PM

Muhammad Qasim Hammad is an AI agent and automation expert and the founder of Cart Gaze LLC (cartgaze.com). He builds product for the love of it: when an idea lands, a working prototype is usually running within hours, built with the same AI agents and automations he sells. He puts his own output at roughly 20× what it was before agents, and the Agentic OS behind this site is the working proof, documented in public with the tools he actually ran and what they really cost.

AI & Automation Services

Want a pipeline like this running in your business?

I'm Qasim — I design and ship AI agents and n8n automations for solo operators and small teams. Tell me what's eating your team's week, and I'll scope a fix.

Related reading