AI Engineer Roadmap All lessons Interactive Quiz MernStackDev GitHub

HNSW

Play the interactive lesson: HNSW indexRead on GitHub
On this page
  1. Explain it like I'm new
  2. Watch it
  3. How it works
  4. Code example
  5. When to use it / when not to
  6. Common mistakes
  7. Recap
  8. Interview questions
  9. Go deeper

Level: Advanced · Reading time: 9 min · Phase: 1 — RAG Foundations

In one line: HNSW (Hierarchical Navigable Small World) is a graph-based index that finds approximate nearest neighbours by hopping from point to closer point, starting on sparse "express" layers and finishing on a dense bottom layer.

Before you start: Vector Databases and Cosine Similarity. You should know what nearest neighbour search is, and that brute force checks every vector.

Explain it like I'm new

Think about travelling from a small village to a friend's house in another country. You do not walk street by street. You drive to a highway, take a flight to the right country, a train to the right city, then walk the last few streets. Each level is coarser and faster than the one below it.

Words to know:

HNSW organizes vectors the same way.

Search starts at the top layer, greedily moves to whichever neighbour is closest to the query, drops down a layer, and repeats. On the bottom layer, which contains every vector, it does a careful local search and returns the best k.

"Small world" refers to networks where any two nodes are connected by a short chain of links, like the "six degrees of separation" idea for people. "Navigable" means a simple greedy walk can find that short chain.

HNSW is one of the most widely used ANN indexes. It is available in FAISS, hnswlib, pgvector, Qdrant, Weaviate, Milvus, Elasticsearch/OpenSearch and others.

Watch it

HNSW index: Hop from point to closer point, from a sparse top layer down to all the points.
HNSW index: Hop from point to closer point, from a sparse top layer down to all the points.

It plays on a loop. Read the caption in the box, one step at a time. Want to go at your own pace? Step through it with Previous and Next.

How it works

Building (insert one vector at a time)

  1. Pick a random top layer for the new node. Most nodes only live on layer 0; each higher layer has exponentially fewer nodes.
  2. Starting from the global entry point at the top, greedily walk down to the new node's top layer.
  3. On each layer from there down to 0, find nearby nodes (keeping a candidate list of size ef_construction) and link the new node to up to M of them. Links go both ways.

Searching

  1. Start at the entry point on the top layer.
  2. Greedy step: move to any neighbour closer to the query; stop when no neighbour is closer. Go down one layer.
  3. On layer 0, run a beam search: keep the ef (or ef_search) best candidates found so far, expand their neighbours, stop when no candidate can improve the list.
  4. Return the top k from that list.
Layer 2:  A ---------------------------- B              (few nodes, long links)
          |                              |
Layer 1:  A -------- C --------- D ----- B              (more nodes)
          |          |           |       |
Layer 0:  A - e - f - C - g - h - D - i - B - j - k     (all nodes, short links)
                                    ^ query lands near here
search: start A (L2) -> B is closer? yes -> drop to L1 -> D closer -> drop to L0 -> beam search around D

Key parameters

ParameterMeaningHigher value means
MLinks per nodeBetter recall, more memory, slower build
ef_constructionCandidate list size when buildingBetter graph, slower build
ef / ef_searchCandidate list size when searchingBetter recall, slower queries

Worked example

The repo demo uses 200 vectors and 50 queries. Brute force compares each query with all 200 vectors (recall@1 = 1.00, 200 computations). The graph index (one layer, same beam search) does this:

(recall@1 = how often the single best result is the true nearest vector.) So a small ef is cheap but misses more often. Raising ef buys accuracy with more work, still less than the 200 of brute force.

Code example

Real HNSW is too long for a lesson. code/rag_hnsw_ivf.py builds a single-layer navigable graph (each node linked to its nearest neighbours plus a few random long links) and searches it with the same beam search HNSW uses on layer 0. It counts distance computations so you can compare with brute force.

from rag_hnsw_ivf import GraphIndex, brute_force_search, make_clustered_data

data = make_clustered_data()                 # 200 2-D points in 4 clusters
graph = GraphIndex(data, m=5, random_links=2)
q = [9.5, 0.3]
print(brute_force_search(q, data, k=1))      # ([id], 200 distance computations)
print(graph.search(q, k=1, ef=8))            # usually same id, far fewer computations

Run python rag_hnsw_ivf.py to see recall@1 and average work for ef = 2, 8, 32. A bigger ef raises recall and costs more distance computations — the same knob as real HNSW.

In real projects (pip install hnswlib numpy):

import hnswlib
import numpy as np

dim, n = 384, 10_000
data = np.random.rand(n, dim).astype("float32")
index = hnswlib.Index(space="cosine", dim=dim)
index.init_index(max_elements=n, M=16, ef_construction=200)
index.add_items(data, np.arange(n))
index.set_ef(50)                                  # ef_search; must be >= k
labels, distances = index.knn_query(data[:3], k=5)

FAISS equivalent: faiss.IndexHNSWFlat(dim, 32).

When to use it / when not to

Flat (brute force)HNSWIVF
RecallExactHigh, tunable with efTunable with nprobe
Query speedSlow at scaleVery fastFast
MemoryVectors onlyVectors + graphVectors + centroids
BuildNoneSlowerNeeds training (k-means)

Common mistakes

Recap

If you remember only three things:

Interview questions

1. Explain HNSW in two sentences. <details><summary>Answer</summary> It builds a multi-layer proximity graph where upper layers are sparse with long links and the bottom layer has all vectors. Search greedily descends from the top layer and finishes with a beam search on the bottom layer. </details>

2. What do M and ef_search control? <details><summary>Answer</summary> M is the number of links per node (recall and memory). ef_search is the size of the candidate list during search (recall versus query latency). </details>

3. HNSW or IVF for 5 million vectors with strict latency needs? <details><summary>Answer</summary> HNSW is usually the first choice if memory allows, because it gives high recall at low latency without training. Choose IVF (often with quantization) when memory is the tighter limit. </details>

Go deeper

Prev: Dot Product · Next: IVF Index · Back to phase: Phase 1