m is graph connectivity, ef_construct is build-time search breadth
m and ef_construct are both build-time parameters and they control two orthogonal things. m is the maximum number of outbound edges each node is allowed to keep on layers above 0; on layer 0 the cap is 2m because edges are stored bidirectionally. It is the graph connectivity, and it directly drives index memory: roughly 2m * 4 bytes per point on layer 0, plus the upper layers. ef_construct is the size of the dynamic candidate list used while inserting a new node. When a new point is added, HNSW runs a search from the entry point down to the new node level with a beam width of ef_construct, then applies the diversity heuristic to pick which of those candidates actually become edges. So m controls how many edges survive, and ef_construct controls how good a pool of candidates those edges are chosen from.
The mechanism matters because it explains why the two parameters are not interchangeable. If you raise m but leave ef_construct small, you are telling the graph to keep more edges but you are only giving it a shallow candidate pool to choose from - the extra edges tend to be redundant near neighbors rather than useful long-range links, so recall improves only marginally. If you raise ef_construct but leave m small, you give the algorithm a better view of the neighborhood at build time but it can only keep a few of those candidates, so the graph quality gain is capped by m. The two parameters work together: ef_construct determines the quality of the decision, m determines how much of that quality you can store. The diversity heuristic is what converts a wider ef_construct into genuinely better navigability, because a wider beam is more likely to surface a distant candidate that survives the heuristic and becomes a long-range edge.
m: higher = denser graph, better recall, more memory, slower build, higher cache pressure at query time. Typical range is 16-64; the Qdrant default is 16.
ef_construct: higher = better graph quality, slower build, no effect on query latency or memory. Typical range is 100-500; the Qdrant default is 100.
Both require a re-index to change. Neither can be tuned per query.
There is a hard trade-off between m and memory: doubling m roughly doubles graph memory on layer 0, which is often the dominant cost at scale.
The trade-off is therefore memory and build time against recall, with no free lunch. My rule of thumb is to set m based on memory budget first, then set ef_construct high enough that the graph is well connected (typically 2-4x m is a reasonable starting point, but measure). The common mistake is assuming ef_construct affects query performance. It does not - it is purely a build-time knob, and if you are trying to fix slow queries by lowering ef_construct you are just going to get a worse index for the same query cost. Another common mistake is setting m very high expecting linear recall gains. In practice recall vs m plateaus: once the graph is connected enough for the intrinsic dimensionality of the data, extra edges add memory and cache misses without improving recall. The alternative to raising m is to raise ef at query time instead, which costs latency but no memory and no rebuild - often the better lever when you cannot afford a re-index. If memory is the binding constraint, IVF-PQ is the alternative index family to consider, trading recall for a much smaller footprint.
Version-dependent: Qdrant has adjusted default m and ef_construct values across releases and the defaults differ by vector size, so read the effective config with client.get_collection() rather than assuming. The update_collection path for hnsw_config has been stable for several releases, but on very large collections the re-index can be effectively an outage, so the production-safe pattern is to create a new collection with the target config and migrate points rather than mutating in place.
0-2 years experience
2-5 years experience
5-8 years experience
8+ years experience