How Dijkstra's Algorithm Works
Dijkstra's algorithm finds shortest paths on a weighted graph by always settling the nearest unsettled vertex. Watch why that greedy choice is safe — and why one negative edge breaks it.
Empty.
Nothing yet.
- Not reached
- Waiting
- Current
- Finished
Step 1 of 20
A is 0 away from itself. Everything else starts at infinity.
Counters
- Settled
- 0/8
- Reached
- 1
- Cost here
- -
- Edges
- 10
Settings
Dijkstra
- 1
dist[start] = 0, everything else inf - 2
while unsettled nodes remain: - 3
node = closest unsettled node - 4
settle(node) - 5
for each neighbour: - 6
d = dist[node] + weight - 7
if d < dist[neighbour]: - 8
dist[neighbour] = d
What each one answers
| Algorithm | What it answers | Time | Needs |
|---|---|---|---|
| Breadth-first | Fewest hops from the start | O(V + E) | A queue |
| Depth-first | Reachability, one branch at a time | O(V + E) | A stack |
| Cycle detection | Does a cycle exist, and where | O(V + E) | Node colours |
| Topological sort | A valid order to do the work in | O(V + E) | No cycles |
| Dijkstra | Cheapest path by edge weight | O(E log V) | Non-negative weights |
V is the number of nodes and E the number of edges. Switch to the graph with a cycle and the topological sort gets stuck: once a cycle exists there is no valid order.
Complexity
| Best | Average | Worst | Extra memory |
|---|---|---|---|
| O((V + E) log V) | O((V + E) log V) | O((V + E) log V) | O(V) |
With a binary heap. A Fibonacci heap gives O(E + V log V) in theory and loses in practice; with weights all equal it degenerates to BFS.
How dijkstra's algorithm works
Dijkstra's algorithm keeps a tentative distance for every vertex and repeatedly settles the unsettled vertex with the smallest one, relaxing its outgoing edges as it goes. The greedy step is safe because all weights are non-negative: no later detour can make a longer path shorter, so the smallest tentative distance cannot be improved by anything still unexplored. A single negative edge destroys exactly that argument, which is why Dijkstra gives wrong answers on negative weights rather than slow ones.
- Set the start's distance to zero and every other vertex's to infinity. Push the start onto a min-priority queue.
- Pop the vertex with the smallest tentative distance. Its distance is now final.
- Relax each outgoing edge: if going through this vertex is cheaper than the neighbour's current distance, update it and push the neighbour.
- Skip any popped entry whose distance is stale — a cheaper route to that vertex was already settled.
- Stop when the queue empties, or early once the destination is popped.
Implementation
The same algorithm the visualiser is running, written to be read.
import heapq
def dijkstra(graph, start):
"""graph: {node: [(neighbour, weight), ...]} with weights >= 0."""
distance = {node: float("inf") for node in graph}
distance[start] = 0
came_from = {start: None}
queue = [(0, start)]
while queue:
cost, node = heapq.heappop(queue)
# A cheaper route to this node was already settled.
if cost > distance[node]:
continue
for neighbour, weight in graph[node]:
candidate = cost + weight
if candidate < distance[neighbour]:
distance[neighbour] = candidate
came_from[neighbour] = node
heapq.heappush(queue, (candidate, neighbour))
return distance, came_fromWhen to use it
- Use it for shortest paths with non-negative weights: road distances, network latency, transit fares, any cost that cannot be negative.
- Use it with an early exit when you only need one destination, and with a heuristic — that is A* — when you have one that never overestimates.
- Avoid it with negative edges. Bellman-Ford handles those and detects negative cycles; Johnson's algorithm reweights a graph so Dijkstra can be used anyway.
- Note that on an unweighted graph BFS gives the same answer in O(V + E) with no heap at all.
Check yourself
If the animation made sense, these should too.
Why does one negative edge break Dijkstra rather than just slow it down?
Because its greedy step assumes that the smallest tentative distance is final - which holds only if no path can get cheaper by being longer. A negative edge makes exactly that possible, so a settled vertex can turn out to have a shorter route, and the algorithm never revisits it.
Why skip a popped entry whose distance is greater than the recorded one?
Because the same vertex can be pushed several times as cheaper routes are found, and the stale entries stay in the heap. Skipping them is what makes the lazy-deletion version correct; without the check, a vertex gets expanded with an out-of-date distance.