Questions
1 of 14
1What does HNSW stand for, and at a high level, how does it achieve sub-linear approximate nearest-neighbor search?
2What do the HNSW parameters m and ef_construct control, and what trade-off do they represent?
3What does the query-time parameter ef (search breadth) control, and how would you use it to trade off recall against latency?
4Why might increasing m significantly improve recall on one dataset but barely help - or even hurt latency - on another?
5Why does Qdrant set m: 0 on a named vector used purely for reranking (e.g. a ColBERT multivector)?
6What problem does vector quantization solve, and what is the fundamental trade-off it introduces?
7Compare scalar quantization, product quantization, and binary quantization in Qdrant in terms of compression ratio and accuracy impact.
8What are oversampling and rescoring in the context of binary quantization, and why are they necessary?
9What newer quantization options - beyond the original scalar, product, and binary trio - has Qdrant introduced to fine-tune the compression/accuracy curve?
10What is Inline Storage, and how does embedding quantized vectors directly into HNSW graph nodes improve disk-based search performance?
11What is a multivector point, and how does it differ from a point with several named vectors?
12How does late-interaction scoring (as used by ColBERT-style models) with MaxSim differ from comparing two single dense vectors?
13Why is late-interaction reranking typically applied to a small candidate set rather than the entire collection?
14Design a three-stage retrieval pipeline using dense retrieval, sparse retrieval, fusion, and ColBERT reranking. What does each stage contribute?
01 / 14

What does HNSW stand for, and at a high level, how does it achieve sub-linear approximate nearest-neighbor search?

HNSW: Hierarchical Navigable Small World graphs

HNSW stands for Hierarchical Navigable Small World. It is the default approximate nearest-neighbor (ANN) index in Qdrant. The name is two ideas glued together. Navigable Small World is the property that a graph can be built so that greedy routing between any two nodes takes a number of hops that grows logarithmically with the number of nodes - you do not need a globally optimal routing table, just a graph where each node has a few long-range links and several short-range ones. Hierarchical is the trick that makes greedy routing actually work in practice: instead of one flat graph, you keep a stack of graphs at decreasing density, and you use the sparse top layers as an express lane to get close to the query region before you start doing expensive fine-grained work at the bottom.

Mechanically, every point is assigned a maximum level drawn from a geometric distribution, so the expected number of points at level l shrinks by a constant factor as l increases. Layer 0 contains every point; each higher layer contains a random subset of the layer below. A search starts at the single entry point at the top layer and performs greedy descent: from the current node it repeatedly jumps to the neighbor closest to the query, stopping when no neighbor improves the distance. It then drops to the next layer down and repeats, using the node it just converged on as the new entry point. This is why search is sub-linear: on each sparse upper layer a single greedy walk covers a large fraction of the metric space in O(log N) hops, and by the time you reach layer 0 you are already in the right neighborhood, so the layer-0 traversal only has to explore a small local region. Layer 0 itself is not pure greedy - it is a best-first search over a candidate priority queue bounded by ef, which is what lets it escape local minima and return a good top-k rather than a single nearest neighbor. The critical detail that most people miss is the neighbor-selection heuristic used at build time: HNSW does not simply connect each node to its M nearest neighbors. It applies a diversity heuristic that keeps an edge only if the candidate is closer to the node than to any already-selected neighbor, which deliberately preserves long-range links and prevents the graph from collapsing into disconnected clusters. Without that heuristic, recall degrades badly on clustered or high-dimensional data.

  1. 1

    Search cost is dominated by the number of distance computations: roughly O(log N) hops on upper layers plus an ef-bounded best-first expansion at layer 0.

  2. 2

    Build-time knobs (m, ef_construct) and query-time knobs (ef) are separate. Changing m or ef_construct requires re-indexing; changing ef does not.

  3. 3

    Memory is the main cost: each node stores up to 2*m links on layer 0 (bidirectional), so a 100M-point collection with m=16 is on the order of tens of gigabytes of graph alone, before vectors.

  4. 4

    HNSW is approximate by construction. Any claim about recall must be measured against exact ground truth on your own data, not assumed from a blog post.

The trade-off you are always making is recall and latency against memory and build time. Raising m makes the graph denser and typically raises recall, but it increases memory linearly and can hurt latency once the graph stops fitting in CPU cache - that is the point where more edges turns into more cache misses rather than fewer hops. Raising ef_construct improves graph quality at build time and costs build throughput, not query latency. Raising ef at query time buys recall for latency, per request, with no rebuild - that is the knob you reach for first when someone reports bad results. The main alternative to HNSW is IVF-style indexing, typically combined with product quantization (IVF-PQ), which uses far less memory and builds much faster but generally gives worse recall at the same latency; it is the right choice when the corpus is huge and memory-constrained rather than latency-constrained. Brute force (or Qdrant exact search) is the right choice below roughly ten thousand vectors, which is why Qdrant has a full_scan_threshold that switches small collections to a linear scan automatically. The most common misconception from less experienced engineers is that ef_construct affects query speed - it does not, it only affects index quality and build time, so tuning it to fix a latency problem is wasted effort and a re-index. The second most common is treating HNSW output as ground truth; in a retrieval system, ANN recall and reranker quality are two independent error sources and you have to measure them separately. A third, subtler one: assuming a higher m is always better. Past a certain point, extra edges mostly add memory and cache pressure with negligible recall gain.

javascript

Version-dependent notes worth flagging in an interview rather than stating as universal truth: the client API here is qdrant-client 1.10+, where query_points replaced the older search/search_batch calls - the old names still work but are deprecated, so pin your client version. The exact default values of m, ef_construct and full_scan_threshold have changed across Qdrant releases and differ per vector size, so read the collection config at runtime instead of hardcoding assumptions. Recent Qdrant releases also added on-disk HNSW with quantized vectors stored inline in the graph nodes, which changes the memory/latency calculus significantly for large on-disk collections; if you are on an older minor version, that option does not exist and your sizing math will be wrong. Finally, setting m to 0 on a named vector disables graph construction entirely for that vector, which is intentional for reranking-only vectors and is not the same as having a broken index.

Difficulty: 5/10
Topics: HNSW, Approximate Nearest Neighbor Search, Graph Index Structure

Scenario Questions

0-2 years experience
  1. 1

    You created a Qdrant collection with default HNSW settings and 2M vectors. Searches return in 4ms but a colleague says the results feel wrong. How do you determine whether the problem is HNSW recall or the embedding model itself?

  2. 2

    You upsert 200k new points into a collection that already has an HNSW index. Are those points searchable immediately, and what is happening to the graph in the background?

2-5 years experience
  1. 1

    Your p99 search latency doubled after you raised ef_construct from 100 to 500 to fix a recall complaint. Explain what actually happened and what you should have changed instead.

  2. 2

    You need to move m from 16 to 32 on a live 50M-point collection serving production traffic. Walk me through your migration plan and what you tell stakeholders about availability.

5-8 years experience
  1. 1

    Design the HNSW configuration for a 200M-vector multi-tenant collection with a hard 20ms p99 and a memory budget that does not fit the full graph in RAM. What do you put on disk, what do you quantize, and how do you keep tenant isolation from destroying graph connectivity?

  2. 2

    Ninety percent of your queries filter on a low-cardinality payload field (e.g. tenant_id) where each value matches only 0.1% of points. Explain how Qdrant filterable HNSW traversal differs from post-filtering, and what you would tune when filtered recall drops even though unfiltered recall is fine.

8+ years experience
  1. 1

    You must demonstrate recall@10 >= 0.95 against exact ground truth on a 500M-vector collection while holding p99 under 30ms at 500 QPS. Describe your measurement methodology, the tuning sequence you would follow, and the point at which you would conclude HNSW is the wrong index for this workload.

  2. 2

    Building HNSW over 1B vectors takes days in a single process. Design a sharded build-and-merge strategy and quantify the recall and latency cost of merging independently built graphs versus building one global graph.

Follow-up Questions

  • If the hierarchy already gives sub-linear search, why does Qdrant still expose an exact search mode and a full_scan_threshold, and when would you deliberately force a full scan in production?
  • How does HNSW handle point deletions - are nodes physically unlinked from the graph, and what does that mean for recall and memory on a collection with high churn?