Dijkstra's Algorithm
Dijkstra's algorithm finds the cheapest route from one start node to every other node in a weighted graph. It grows a settled region outward, always locking in the closest unsettled node next - because no undiscovered path could ever reach that node more cheaply. This cheapest-first rule is what your GPS runs (in souped-up form) every time you ask for directions. Think of it as BFS for weighted worlds: BFS counts hops, Dijkstra adds up costs.
Watch it settle the cheapest first
Press play - or drag the timeline and step through it yourself.Find the cheapest route from A to every node. Edge numbers are costs. The rule: always settle the closest unsettled node - its cost can never improve again.
Complexity
A road trip with a notebook
You are in city A and want the cheapest travel cost to every city on the map. In a notebook you write the best price you know so far for each city - at first only A costs 0, everything else is unknown. Now repeat one move: find the cheapest uncircled price in the notebook and circle it. That price is final, because any other way into that city would have to pass through somewhere already more expensive. Then check the circled city's direct roads: does going through it make any neighbour cheaper? If yes, cross out the old price and write the better one. When every city is circled, the notebook holds the true cheapest cost to everywhere.
How it works, step by step
Give every node a tentative distance: 0 for the start, ∞ for everyone else.
Pick the unsettled node with the smallest tentative distance and settle it. Its distance is now final.
Why is that safe? Any other route into it must leave the settled region through a node that is already at least as far away - and with no negative weights, the route cannot get cheaper from there.
Relax each edge leaving the settled node: if `dist[u] + weight < dist[v]`, you found a cheaper way to v - update its distance and remember the edge you came through.
Repeat until every node is settled. The remembered edges form a shortest-path tree: one best route from the start to every node.
With a priority queue doing the "pick the smallest" step, the total work is O(E log V).
The code, in JavaScript
The honest, readable version: a plain linear scan finds the closest unsettled node each round. Up to a few thousand nodes this is genuinely fine - and there is nothing in it to get wrong.
function dijkstra(graph, start) {
const dist = {};
const settled = new Set();
for (const node in graph) dist[node] = Infinity;
dist[start] = 0;
for (let i = 0; i < Object.keys(graph).length; i++) {
// Linear scan: the unsettled node with the smallest tentative distance.
let u = null;
for (const node in graph) {
if (!settled.has(node) && (u === null || dist[node] < dist[u])) u = node;
}
if (dist[u] === Infinity) break; // everything left is unreachable
settled.add(u); // dist[u] is final from here on
for (const [v, w] of graph[u]) {
if (dist[u] + w < dist[v]) {
dist[v] = dist[u] + w; // relax: found a cheaper way to v
}
}
}
return dist;
}
const graph = {
A: [["B", 10], ["C", 2]],
B: [["A", 10], ["C", 3], ["D", 4]],
C: [["A", 2], ["B", 3], ["D", 4], ["E", 7]],
D: [["B", 4], ["C", 4], ["E", 1], ["F", 6]],
E: [["C", 7], ["D", 1], ["F", 2], ["G", 6]],
F: [["D", 6], ["E", 2], ["G", 2]],
G: [["E", 6], ["F", 2]],
};
dijkstra(graph, "A");
// → { A: 0, B: 5, C: 2, D: 6, E: 7, F: 9, G: 11 }
// Note B: the 2-hop route A→C→B (2+3=5) beats the direct A→B road (10).Dry run: Dijkstra from node A
The same steps the visualizer plays, written as a table. Generated by running the real algorithm - not written by hand.
| Step | Settled | What happened |
|---|---|---|
| 1 | 1 | A is the closest unsettled node (d=0). Settle it and relax its edges. |
| 2 | 1 | First route to B: d=10, via edge A-B. |
| 3 | 1 | First route to C: d=2, via edge A-C. |
| 4 | 2 | The closest unsettled node is C (d=2), not B (d=10). Cheapest first - settle C, its cost is final. |
| 5 | 2 | Found a cheaper way to B: 10 → 5. Two hops through C beat the direct road from A. |
| 6 | 2 | First route to D: d=6, via edge C-D. |
| 7 | 2 | First route to E: d=9, via edge C-E. |
| 8 | 3 | The closest unsettled node is B (d=5). Settle it - no cheaper route to B can exist. |
| 9 | 3 | 9 is not cheaper than 6 - D keeps its current route. |
| 10 | 4 | The closest unsettled node is D (d=6). Settle it - no cheaper route to D can exist. |
| 11 | 4 | Found a cheaper way to E: 9 → 7. E's route now comes through D. |
| 12 | 4 | First route to F: d=12, via edge D-F. |
| 13 | 5 | The closest unsettled node is E (d=7). Settle it - no cheaper route to E can exist. |
| 14 | 5 | Found a cheaper way to F: 12 → 9. F's route now comes through E. |
| 15 | 5 | First route to G: d=13, via edge E-G. |
| 16 | 6 | The closest unsettled node is F (d=9). Settle it - no cheaper route to F can exist. |
| 17 | 6 | Found a cheaper way to G: 13 → 11. G's route now comes through F. |
| 18 | 7 | G is the last node left (d=11). Settle it. |
Good choice when…
- You need the cheapest route and edge costs are non-negative: roads, network latency, fares, travel time.
- One start, many destinations - a single run gives the cheapest cost to every node, which is how GPS and routing tables use it.
- Edge costs differ. The moment weights are unequal, BFS's fewest-hops answer stops being the cheapest one.
- You only care about one target: stop the moment it settles, and skip the rest of the graph for free.
Bad choice when…
- Any edge weight can be negative - the settled-is-final promise breaks. That is Bellman-Ford's job.
- All edges cost the same. Plain BFS gives the identical answer with a plain queue and less machinery.
- You want the cheapest way to wire all nodes together (cables, pipes). That is a minimum spanning tree - Kruskal - not a shortest-path tree; the two trees can differ.
Common mistakes
- One negative edge quietly breaks the settled-is-final promise. Take directed edges A→B=2, B→D=2, A→C=5, C→B=-4. Dijkstra settles B at 2 and computes D=4. The cheaper way to B (via C, cost 1) is found too late: B is already settled, so D is never re-checked. The true cost of D is 1+2=3. Negative weights are Bellman-Ford's job.
- JavaScript has no built-in priority queue, so people improvise: `queue.sort()` every iteration, or a minimum-scan of an array inside the main loop. It returns correct answers - and quietly ships the O(V²) version. Fine at 1,000 nodes, painful at 1,000,000.
- You do not need decrease-key. Textbook heaps update a node's priority in place, which is fiddly to code. The practical trick is lazy deletion: on improvement just push a new [dist, node] pair and skip stale pairs on pop. The heap grows to O(E) entries, but O(E log E) is still O(E log V).
- With lazy deletion, forgetting `if (d > dist[u]) continue` still returns correct distances - which is why nobody notices. But every stale pop re-relaxes all of u's edges and pushes even more duplicates. The complexity quietly inflates, and on dense graphs it shows up as mystery slowness.
Dijkstra's Algorithm vs. its closest relatives
| Algorithm | Best | Average | Worst | Space | Stable |
|---|---|---|---|---|---|
| Dijkstra's Algorithmthis page | O(E log V) | O(E log V) | O(E log V) | O(V) | - |
| Breadth-First Search | O(V + E) | O(V + E) | O(V + E) | O(V) | - |
| Bellman–Ford | O(E) | O(V·E) | O(V·E) | O(V) | - |
| Kruskal's MST | O(E log E) | O(E log E) | O(E log E) | O(V) | - |