Learn › Search & Optimization
·FREE·PAPER·8 min read

Graph Algorithms from Scratch — Shortest Paths and Where They Lead

From transit apps to vector search, the world runs on dots and lines. We build up BFS, Dijkstra, and A* assuming zero background, then follow one unbroken thread all the way to HNSW — the graph search powering retrieval in the LLM era.

Key points

  • A graph captures only what connects to what, using vertices and edges, and the shortest path is its most fundamental question.
  • BFS finds the route with the fewest edges; Dijkstra's algorithm finds the route with the smallest total edge weight.
  • Dijkstra finalizes the smallest-tentative-distance vertex via a priority queue, fails on negative weights, and runs in O((V+E)log⁡V)O((V+E)\log V).
  • A* scores vertices by f(n)=g(n)+h(n)f(n)=g(n)+h(n), guarantees a shortest path if hh never overestimates, and equals Dijkstra when h(n)=0h(n)=0.
  • HNSW is approximate nearest-neighbor search that greedily walks a hierarchical graph of near vectors, and `efSearch` trades recall against latency.
ModalitytextTaskalgorithms

Efficient and robust approximate nearest neighbor search using Hierarchical Navigable Small World graphs

Primary source — what this article is built on

undefined2026-08-13

Efficient and robust approximate nearest neighbor search using Hierarchical Navigable Small World graphsarXiv:1603.09320Paper page·PDF

The world is made of dots and lines

Picture a subway map. Stations are dots; the tracks connecting them are lines. The map ignores real distances and geography entirely — all it records is what connects to what. This structure, stripped down to pure connectivity, is what mathematicians call a graph. The dots are vertices (or nodes), the lines are edges.

Graphs go far beyond subway maps. Social networks (people are vertices, follows are edges), web pages and hyperlinks, road networks, atoms and chemical bonds, even the layers of a neural network — almost any "relationship between things" can be written as a graph. If the relationship has a direction (a follow is one-way), the graph is directed; otherwise it is undirected. If each edge carries a number like travel time or distance, it is a weighted graph.

The most fundamental — and most frequently asked — question about a graph is the subject of this article: the shortest path. "What is the cheapest way to get from A to B?" Car navigation, transit routing, enemy AI chasing you through a game level, and (as we'll see later) vector search all boil down to this one question.

"Shortest" comes in two flavors

There are actually two distinct versions of the problem:

BFS solves the first; Dijkstra's algorithm solves the second. Keep this distinction in mind and you'll never reach for the wrong tool.

BFS: search that spreads like ripples

Breadth-First Search (BFS) works like a stone dropped into a pond. From the starting point, you visit everything one step away, then everything two steps away, and so on — rings expanding outward in order of distance.

The only tool you need is a queue (a first-in, first-out waiting line). Put the start vertex in the queue; repeatedly take one out and append each unvisited neighbor, marking it as seen. That's the whole algorithm. Because the ripple expands in order of distance, the moment you first reach a vertex, the number of steps taken is already the shortest hop count to it. No shorter route can turn up later.

With VV vertices and EE edges, the cost is O(V+E)O(V + E) — each vertex and edge is touched at most once, essentially linear time. Compare that with the naive approach of enumerating every possible path and picking the shortest: the number of paths explodes combinatorially, giving exponential time. Drag nn in the figure below to feel just how different those two regimes are.

FIG 1Enumerating all paths lives on the exponential O(2ⁿ) curve; BFS lives on the linear one. As n grows, the gap stops being "fast vs. slow" and becomes "finishes vs. never finishes"

Dijkstra's algorithm: locking in answers, cheapest first

Add weights to the edges and BFS breaks. A route with 2 edges totaling 10 minutes can lose to a route with 3 edges totaling 6 minutes — but BFS only counts edges. In a weighted world, "expand rings in order of nearness" still works, but "near" has to be re-measured in accumulated cost rather than step count.

Enter Dijkstra's algorithm, devised by Edsger Dijkstra in 1956. The mental image: pour water on the starting vertex and watch the terrain flood, lowest ground first. The algorithm repeatedly extracts the vertex with the smallest tentative distance and declares that distance final. The tool is no longer a plain queue but a priority queue — a waiting line that always hands you the smallest item. After finalizing a vertex uu, for each neighbor vv we attempt this update:

d(v)←min⁡(d(v), d(u)+w(u,v))d(v) \leftarrow \min\bigl(d(v),\ d(u) + w(u,v)\bigr)
(1)

In plain words: "compare the best distance to vv we've recorded so far, d(v)d(v), against the length of the new route that enters vv through the just-finalized uu, namely d(u)+w(u,v)d(u)+w(u,v) — and keep whichever is smaller." Here w(u,v)w(u,v) is the weight of the edge from uu to vv. This one-line update is called relaxation.

Why is it safe to finalize the vertex with the smallest tentative distance? Because as long as every edge weight is non-negative, no detour discovered later can possibly beat it — any other route would have to pass through territory that is already at least as far away. When that assumption fails — when some edges have negative weights — Dijkstra silently produces wrong answers, and you need a different tool (the Bellman–Ford algorithm). With a binary-heap priority queue the running time is O((V+E)log⁡V)O((V+E)\log V), comfortably practical at millions of vertices.

A*: search that knows which way the goal is

Dijkstra has one inefficiency: asked for a route from Tokyo to Osaka, it dutifully floods toward Sendai too, because it expands its ring in every direction.

A</strong>* (pronounced "A-star") injects a sense of direction. Each vertex nn is scored as follows, and the priority queue always pops the vertex with the lowest score:

f(n)=g(n)+h(n)f(n) = g(n) + h(n)
(2)

In plain words: "the promise of vertex nn, f(n)f(n), is the distance actually walked from the start to nn, g(n)g(n), plus an estimate of the remaining distance from nn to the goal, h(n)h(n)." The function hh is called a heuristic. On a map, the classic choice is straight-line distance to the goal: roads may twist and turn, but they can never be shorter than the straight line, so this estimate never overestimates the true remaining distance.

As long as that never-overestimate property (called admissibility) holds, A* is guaranteed to find a shortest path — while exploring far fewer vertices than Dijkstra, because the search cone is bent toward the goal. And notice: set h(n)=0h(n)=0 and A* collapses back into Dijkstra exactly. Dijkstra is simply "A* with zero intuition" — the two are not different algorithms so much as two points on one continuum.

Dijkstra in code

With Python's heapq (a binary heap), the core is about ten lines:

import heapq

def dijkstra(graph, start):          # graph[u] = [(v, w), ...]
    dist = {start: 0}
    pq = [(0, start)]                # (tentative distance, vertex)
    while pq:
        d, u = heapq.heappop(pq)     # finalize the current minimum
        if d > dist.get(u, float("inf")):
            continue                 # stale entry — skip it
        for v, w in graph[u]:
            if d + w < dist.get(v, float("inf")):
                dist[v] = d + w      # relaxation
                heapq.heappush(pq, (d + w, v))
    return dist

To turn this into A*, change the key you push from d + w to d + w + h(v). The skeleton stays identical.

And on to vector search — HNSW as applied graph traversal

Now for the payoff. In RAG and recommendation systems, the beating heart is nearest-neighbor search: documents or products become embedding vectors, and every query asks "which stored vectors are closest to this one?" The brute-force answer — measure the distance to every vector — is fine until the collection reaches tens of millions, at which point a full scan per query stops being affordable.

This is where HNSW (Hierarchical Navigable Small World, proposed in the paper listed as this article's primary source) comes in. The idea: ahead of time, build a graph that connects each vector to its near neighbors. At query time, start at some entry vertex and repeat one move: "among my current vertex's neighbors, step to whichever is closest to the query." When no neighbor gets you any closer, you've arrived at a candidate answer. You are walking a graph guided by an estimate of closeness — a direct cousin of the idea behind A*.

The "H" is for hierarchy. HNSW stacks progressively sparser graphs on top of the base layer; a search takes long-range hops in the top layers to get roughly close, then descends layer by layer to fine-tune — like taking the highway to the right city, then surface streets to the exact address. (Data-structure fans will recognize the skip list.) In practice this finds neighbors with far fewer distance computations than a full scan. The catch: because the walk is greedy, it is an approximate method that can occasionally miss the true nearest neighbor. The standard countermeasure is to carry a beam of the best ef candidates instead of a single walker — and that ef knob is exactly what matters in production, as we'll see next.

The "closeness" being measured is inner product, cosine similarity, or Euclidean distance. You can build intuition for that ruler in the figure below.

FIG 2Rotate the vectors and watch the dot product and cosine change. HNSW walks its graph guided by exactly this notion of closeness, stopping where no step gets it any closer

How this is used in the field

When search or RAG engineers choose an index. Nearly every major vector database — Faiss, pgvector, Qdrant, Milvus, OpenSearch — ships an HNSW implementation. Three parameters do most of the work: M, the number of edges per vertex (memory vs. accuracy); efConstruction, the candidate-list size while building (index quality); and efSearch, the candidate-list size while querying (recall vs. latency). The standard tuning ritual is to sweep efSearch upward on real data and find where recall plateaus.

Pitfall 1: blaming the distance function for low recall. When RAG fails to retrieve an obviously relevant document, check whether efSearch (sometimes just ef) is still at its default before suspecting the embedding model. Misses are inherent to approximate search, and raising the candidate count often makes them vanish.

Pitfall 2: mismatched distance metrics. If you intend cosine similarity but feed unnormalized vectors into an inner-product index, the results skew toward whatever has the largest norm. "Does the metric at query time match the metric at build time?" belongs at the top of any incident checklist.

Pitfall 3: Dijkstra on negative weights. The moment someone models a discount or reward as a negative edge cost, the algorithm's premise collapses and it returns wrong answers without raising any error. If negative edges are part of the design, switch to the Bellman–Ford family.

For game and mapping engineers, reviewing whether an A* heuristic is admissible (never overestimates) is a required checkpoint. It is also a perennial interview question: "what happens if hh overestimates?" — answer: the search gets faster, but the shortest-path guarantee breaks.

Takeaways

  • A graph is the universal language of "dots and lines," and the shortest path is its most important question
  • BFS solves fewest-edges with a queue; Dijkstra solves weighted-shortest with a priority queue and relaxation
  • A* bends the search toward the goal using an estimate that never overestimates; with h=0h=0 it is Dijkstra
  • HNSW greedily walks a graph of near neighbors, guided by closeness — the same graph-search skeleton, now holding up retrieval in the LLM era

For how to read the big-O notation used here, see the complexity basics; for queues and heaps themselves, the data structures tour; and for the full retrieval system HNSW plugs into, RAG fundamentals.

🎬 Watch this explained (Manim, captioned)

References

  1. Efficient and robust approximate nearest neighbor search using Hierarchical Navigable Small World graphs. arXiv:1603.09320Paper page·PDF

This article is written from the source paper above. Where they differ, the original is authoritative.

Published by TechAthletes · Editorial policy · How we cite · Corrections

Comments

Sign in to comment