When would you choose FAISS IndexHNSWFlat over IndexIVFFlat?
answer
- One trains, one does not
- Graph links cost extra memory
- Deletion support is the hidden difference
- Insertion cost versus scan cost
- They also compose as quantizer plus lists
basics
~20 sChoose IndexHNSWFlat for low-latency single queries on a mostly static, moderately sized corpus that fits in RAM, since it needs no training and gives strong recall per unit of latency. Choose IndexIVFFlat when memory matters, the corpus is large, or entries must be deleted.
solid answer
~50 sThe two differ on four axes. **Training**: `IndexHNSWFlat(d, M)` reports `is_trained` True and can accept vectors immediately, while `IndexIVFFlat(quantizer, d, nlist)` must be trained on a representative sample before any `add()`. **Memory**: HNSW stores the full vectors *plus* the graph links, roughly `4*d + M*2*4` bytes per vector, so at M=32 it costs a few hundred bytes more per vector than IVF, which stores vectors plus an 8-byte id. **Mutability**: IVF implements `remove_ids`; FAISS's HNSW does not, so deletions mean rebuilding or filtering results in your application. **Build cost**: HNSW insertion is comparatively slow and hard to parallelise across a huge corpus, while IVF adds are cheap once trained. In practice HNSW wins at the hundreds-of-thousands to low-millions scale with a strict latency SLO; IVF wins when the corpus is large, churny, or memory-constrained. The two also compose — `"IVF65536_HNSW32,Flat"` uses an HNSW graph as the IVF coarse quantizer.
code
python · 10 linesimport faiss
d = 256
hnsw = faiss.IndexHNSWFlat(d, 32) # M = 32 links per node
print(hnsw.is_trained) # True - no training step
hnsw.hnsw.efConstruction = 80
quantizer = faiss.IndexFlatL2(d)
ivf = faiss.IndexIVFFlat(quantizer, d, 4096)
print(ivf.is_trained) # False - train() required firstgo deeper
Know that both are approximate indexes over uncompressed vectors, that IVF must be trained first, and that HNSW can accept vectors straight away.
Explain the structural difference — graph traversal versus scanning nearest partitions — and that HNSW pays extra memory for its links while IVF pays a training step.
Bring the operational axes: deletion support, build time, single-query versus batch latency, and the fact that you set the accuracy knob by measuring recall on your own data.
Own the lifecycle decision — rebuild cadence, swap strategy, and when the corpus outgrows an uncompressed index entirely — rather than treating this as a one-time structure choice.
## Two different bets Both indexes are approximate and both keep vectors uncompressed, so this is not a memory-versus-accuracy question — it is a question about the shape of the workload. HNSW builds a navigable multi-layer graph over the vectors and answers a query by walking it. IVF partitions the space into `nlist` cells with a coarse quantizer and answers a query by scanning the cells nearest to it. Everything that differs downstream follows from those two structures. ## Training `IndexIVFFlat` needs `train()` before `add()`, because its centroids are learned by clustering a sample. That imposes a pipeline: collect a representative sample, train, then ingest. If the sample is skewed — one tenant, one language, one week — the centroids fit that slice and everything else clusters badly, and the resulting recall loss is invisible until someone measures it. `IndexHNSWFlat` has nothing to learn. `is_trained` is True on construction and you can add vectors from an empty state. For a system that starts with no data and grows, or one where you cannot easily obtain a representative sample up front, that is a genuine operational simplification. ## Memory HNSW is the more expensive of the two per vector. It stores the raw float32 vectors, exactly as flat does, and additionally the graph adjacency: on the order of `M * 2 * 4` bytes per vector for the base layer at connectivity `M`, plus the sparse upper layers. At d=768 and M=32 that is roughly 3072 bytes of vector plus a few hundred bytes of links. IVF stores the raw vectors, an int64 id per entry, and a centroid table of `nlist * d * 4` bytes. Per vector that is essentially the flat cost plus 8 bytes. So on the same corpus HNSW is meaningfully larger, and it does not compress — which is why very large corpora end up on IVF with a compressed encoding rather than on HNSW at all. ## Build time and ingestion Inserting into HNSW means searching the existing graph to find neighbours and then wiring links, for every vector. That is far more work than IVF's "find the nearest centroid, append to that list", and it does not batch as cleanly. Building an HNSW over tens of millions of vectors is measured in hours; the same corpus into a trained IVF index is much faster. The construction quality knob, `index.hnsw.efConstruction`, trades build time for graph quality — raising it makes both the build slower and the resulting recall better. ## Deletions and churn This is the axis people discover too late. `IndexIVFFlat` implements `remove_ids`, so an entry can be dropped from its inverted list. FAISS's HNSW implementation does not support removal — pulling a node out of the graph would strand its neighbours and break navigability. If your corpus has real deletes, HNSW means either periodic full rebuilds with an atomic swap, or maintaining a tombstone set in the application and over-fetching so that filtered results still fill the top-k. Both are workable, both are work, and neither should be discovered in production. ## Latency profile HNSW's strength is single-query latency at high recall. Graph traversal touches a small, adaptive number of vectors and reaches high recall with less work than IVF typically needs, especially for one query at a time — which is the shape of an interactive search or RAG request. IVF's scan is embarrassingly parallel and batches beautifully, so for large batch workloads on many cores the gap narrows or reverses. Both expose a knob that trades accuracy for speed — `index.hnsw.efSearch` for the graph, `index.nprobe` for the lists — and the honest answer is that you set it by measuring recall against a flat ground truth for your data, not by copying a number from a benchmark on someone else's. ## They compose The interesting production answer is that this is not always an either/or. Once `nlist` is large, IVF's coarse quantizer — which by default brute-forces over all centroids — becomes a bottleneck, and the standard fix is to make the quantizer itself an HNSW index: `faiss.index_factory(d, "IVF65536_HNSW32,Flat")`. Here HNSW is not the storage structure; it is the routing structure over 65536 centroids. Recognising that composition is a strong signal, because it shows you understand what each structure is actually for. ## A decision sketch - Corpus under a few million, fits in RAM, near-static, strict p99 on single queries: **HNSW**. - Corpus large, or memory-tight, or you will need compression later: **IVF**, with a `Flat` encoding while it fits and a compressed encoding when it stops fitting. - Frequent deletions: **IVF**, or accept a rebuild pipeline. - No representative training sample available at day zero: **HNSW**, or start flat and migrate. - Very large `nlist`: **both**, with HNSW as the coarse quantizer.
- How do you handle deletions if you have already committed to an HNSW index?Two options. Keep a tombstone set of deleted ids in your application, over-fetch beyond k, and drop tombstoned results before returning — cheap, but recall degrades as the tombstone set grows. Or rebuild periodically into a fresh index and swap it into serving atomically. Most teams do both: tombstones for immediacy, scheduled rebuilds to reclaim the space and the quality.
- Why would you make the IVF coarse quantizer an HNSW index?The default coarse quantizer is a flat index over the centroids, so every query brute-forces all of them. At small nlist that is trivial, but at 65536 or more centroids it starts to dominate query time. Replacing it with a graph index — the factory spelling IVF65536_HNSW32 — makes centroid lookup approximate but far cheaper, which is what makes very large nlist values practical.
- Both indexes store raw vectors. Why is HNSW the larger of the two?Because it stores the graph on top of the vectors. Each node keeps a neighbour list on the base layer plus links on sparser upper layers, on the order of M times a few bytes per vector. IVF adds only an 8-byte id per entry plus a centroid table sized nlist times d times 4. On the same corpus HNSW therefore carries a per-vector overhead that IVF does not.
saying these in an interview costs you the question
- Says HNSW must be trained like IVF does
- Claims HNSW uses less memory because it is a graph
- Assumes deletions work the same way in both indexes
- Picks HNSW for tens of millions of vectors on memory grounds
- Treats the choice as purely about query speed