Dijkstra algorithm explained

Dijkstra algorithm explained clearly: the greedy invariant, step-by-step trace, and when to choose it over Bellman-Ford or BFS.

10 minutes
Intermediate
What you will learn

What makes Dijkstra's algorithm correct (the greedy settlement invariant)

How to trace the algorithm step by step on a weighted graph

Why negative edge weights break the algorithm's guarantee

When to choose Dijkstra over Bellman-Ford, Floyd-Warshall, or BFS

Search for "Dijkstra algorithm explained" and you'll find the same recipe everywhere. Pick the node with the smallest distance, update its neighbours, repeat. You can memorise those steps in twenty minutes. But when an interview problem changes the graph structure or adds a constraint you haven't seen, memorised steps fall apart.

The piece most explanations skip isn't the procedure. It's the invariant that makes the procedure correct. Without that, you're following a recipe you can't adapt, can't prove, and can't debug when it gives wrong answers.

TL;DR
Dijkstra's algorithm works because of one guarantee: in a graph with non-negative edge weights, the node with the smallest tentative distance is permanently settled the first time it's popped from the priority queue. Once you understand this invariant, you can derive the algorithm from scratch, prove it correct, and know exactly when it breaks.

What Dijkstra's algorithm actually solves

Dijkstra's algorithm finds the shortest path from a single source node to every other node in a weighted graph with non-negative edge weights. It works by repeatedly selecting the unvisited node with the smallest tentative distance and permanently settling it. This greedy approach is correct because non-negative weights guarantee that no future path through an unsettled node can produce a shorter route.

The problem it solves is single-source shortest path on weighted graphs. Not all-pairs shortest path (Floyd-Warshall's territory) and not shortest path with negative weights (Bellman-Ford). One specific variant, solved efficiently.

If you've studied BFS and DFS, there's a direct connection. BFS finds shortest paths in unweighted graphs because every edge has equal weight. The first time you reach a node through BFS, that's the shortest path. Dijkstra extends this to weighted graphs by replacing the FIFO queue with a priority queue ordered by tentative distance. Instead of processing nodes in discovery order, you process them in order of accumulated weight.

That substitution is the entire algorithm.

If a problem gives you an unweighted graph, BFS is simpler and equally correct. If edges carry different weights, BFS gives wrong answers and Dijkstra is what you need.

ℹ️ Info
Edsger Dijkstra conceived this algorithm in 1956 and published it in 1959. It remains one of the most frequently tested graph algorithms in coding interviews at Google, Amazon, and Meta.

Why Dijkstra's algorithm works: The invariant explained

The claim is simple. When you pop a node from the priority queue, its shortest distance is final. No path discovered later will produce a shorter route to that node.

Why? Because every edge weight is non-negative. When node u is popped with tentative distance d(u), every other node still in the queue has a tentative distance >= d(u). Any alternative path to u would need to go through one of those unsettled nodes first, adding at least their distance (already >= d(u)) plus the connecting edges (>= 0). The result can't be smaller than d(u).

That's the greedy settlement invariant. Not complicated, but it's the reason the whole algorithm works. It also immediately reveals the limitation: the proof depends on non-negative edges. Remove that constraint, and the proof collapses.

Why negative weights break everything

Consider three nodes (A, B, C) with edges A→B (weight 1), A→C (weight 3), and C→B (weight -5).

Starting from A, Dijkstra pops B first (distance 1) and settles it. But there's a path A→C→B with total weight 3 + (-5) = -2, which is shorter than 1. Dijkstra never discovers this because B was already settled. The invariant assumed no future path could beat distance 1. With a negative edge, that assumption fails.

This isn't a theoretical edge case. Interview problems sometimes include negative weights specifically to test whether you understand why Dijkstra doesn't work there, not just that it doesn't. The invariant gives you the answer in one sentence. The greedy settlement guarantee requires non-negative edges because a path through an unsettled node with a negative edge can produce a shorter total distance than the already-settled value.

“The algorithm's correctness isn't a property of its steps. It's a property of the weight constraints.”
The greedy settlement invariant

Dijkstra's algorithm explained step by step

Consider a weighted graph with 5 nodes, labeled A through E.

  • A→B (4), A→C (1)
  • B→E (4)
  • C→B (2), C→D (5)
  • D→E (1)

Starting from node A, we want the Dijkstra shortest path to every other node.

  1. Python

Walk through each step.

Dijkstra trace (source = A)
1
Initialise
dist = {A:0, B:∞, C:∞, D:∞, E:∞}. Push (0, A) to the priority queue.
2
Pop A (distance 0), settle it
Update neighbours: B gets distance 4, C gets distance 1. PQ: [(1,C), (4,B)].
3
Pop C (distance 1), settle it
B improves from 4 to min(4, 1+2) = 3. D gets distance 6. PQ: [(3,B), (4,B), (6,D)].
4
Pop B (distance 3), settle it
E gets distance 3+4 = 7. PQ: [(4,B), (6,D), (7,E)]. The stale (4,B) gets skipped later.
5
Pop D (distance 6), settle it
E is at 7, and 6+1 = 7 offers no improvement. PQ: [(7,E)].
6
Pop E (distance 7), settle it
All nodes settled. Final: {A:0, B:3, C:1, D:6, E:7}.

Notice step 3. B's tentative distance dropped from 4 to 3 because the path A→C→B (total weight 3) is shorter than the direct edge A→B (weight 4). The priority queue naturally surfaces the shorter path first.

That's the invariant in action. By the time B is popped at distance 3, no remaining path can beat it.

💡 Tip
The if u in settled: continue check handles stale entries. When a node's distance improves, the implementation pushes a new entry rather than updating the existing one. The old entry gets skipped when it's eventually popped.

Dijkstra time complexity

With a binary heap (Python's heapq), each push and pop takes O(log V). Every edge triggers at most one push, and every node is popped at most once due to the settled check. That gives O((V + E) log V) total, where V is the number of vertices and E is the number of edges.

With a Fibonacci heap, this improves to O(V log V + E), but the constant factors make Fibonacci heaps rarely practical outside textbooks. In interviews, the binary heap version is what you'll implement and what interviewers expect you to analyse.

When to use Dijkstra (and when not to)

The decision framework for shortest path algorithms is smaller than you'd expect. Three questions narrow it down: Are there negative weights? Do you need single-source or all-pairs? Is the graph weighted at all?

Don't use Dijkstra when
Use Dijkstra when
The graph has negative edge weights (use Bellman-Ford)
All edge weights are non-negative
You need all-pairs shortest paths (use Floyd-Warshall)
You need single-source shortest paths
The graph is unweighted (BFS is simpler and equally correct)
You want O((V + E) log V) over O(VE) from Bellman-Ford
You need to detect negative cycles (Bellman-Ford handles this)
The graph is sparse enough that heap operations stay efficient

One thing that trips people up: some interview problems involve weighted grids where each cell has a traversal penalty, and you need the minimum-weight path from one corner to the other. That's Dijkstra. Engineers who've only practised BFS on unweighted grids sometimes try to force BFS here and get wrong answers. The grid cells carry different weights, so the underlying structure is a weighted graph. Once you recognise that "varying cell weights" maps to "weighted edges," the right algorithm becomes obvious.

Most tutorials don't train that recognition. You learn Dijkstra's steps on an abstract graph but never practise spotting it in disguise. On Codeintuition, the identification lesson for Dijkstra focuses on exactly this. Before you solve any Dijkstra problem, you learn the structural triggers that tell you the pattern applies. The problems that follow (Minimum Cost Path, Cheapest Flights, Teleporter Grid) reinforce those triggers under increasing difficulty.

Common mistakes and how to avoid them

  • Not checking for stale priority queue entries: Without the settled check, you'll process nodes multiple times. The algorithm still produces correct distances, but time complexity degrades from O((V + E) log V) to potentially O(V² log V) on dense graphs.
  • Using Dijkstra on graphs with negative weights: It won't crash. It won't throw an error. It'll silently produce wrong answers for some nodes, and you won't know unless you test against a known solution. If you can't guarantee non-negative weights, switch to Bellman-Ford.
  • Confusing Dijkstra with BFS: Both use a queue, both process nodes by distance. But BFS uses a FIFO queue (correct for unweighted graphs) and Dijkstra uses a priority queue (correct for weighted graphs). Wrong queue type, wrong results.
  • Forgetting to initialise distances to infinity: The source starts at 0, everything else at infinity. Initialise all nodes to 0 and the relaxation step won't update anything, because no path "improves" on a distance of zero.
  • Returning distances when the problem asks for paths: Dijkstra computes shortest distances. If the problem wants the actual path (the sequence of nodes), you need a predecessor map built during relaxation. Track which node caused each update, then backtrack from the destination to reconstruct the route.

If you want frame-by-frame visual walkthroughs across different graph configurations, the understanding lesson on Codeintuition traces every variable at every step. The graph course covers Dijkstra alongside Bellman-Ford and Floyd-Warshall as part of Codeintuition's learning path ($6.67/month with the annual plan), building the full shortest path decision framework in sequence.

Before reading this, you probably knew the steps. Now you know why each step is correct, where the proof breaks, and which problems are Dijkstra in disguise. Next time you see a problem that doesn't look like a textbook shortest-path graph, check for non-negative weighted edges. If they're there, you already know the algorithm.

Do you want to master data structures?

Try our data structures learning path made of highly visual and interactive courses. Get hands on experience by solving real problems in a structured manner. All resources you would ever need in one place for FREE

With a binary heap, Dijkstra runs in O((V + E) log V), where V is the number of vertices and E is the number of edges. Each node gets popped at most once and each edge triggers at most one push, giving the V log V + E log V bound. A Fibonacci heap reduces this to O(V log V + E), but binary heaps remain the interview standard because of lower constant factors and simpler implementation. For dense graphs where E approaches V², the difference between heap types matters more, but interviewers rarely test that distinction.
No. Dijkstra relies on the greedy settlement invariant: when a node is popped, its distance is final. Negative weights violate this because a longer initial path through an unsettled node could produce a shorter total distance. For graphs with negative weights, use Bellman-Ford, which runs in O(VE) but correctly handles negative edges and detects negative cycles.
BFS finds shortest paths in unweighted graphs by processing nodes in discovery order using a FIFO queue. Dijkstra handles weighted graphs by using a priority queue ordered by tentative distance instead. In a weighted graph, BFS gives wrong answers because it ignores edge weights.
Use Dijkstra whenever all edge weights are non-negative and you need single-source shortest paths, since it runs in O((V + E) log V) versus Bellman-Ford's O(VE). Switch to Bellman-Ford only when the graph has negative weights or when you need to detect negative cycles.
The algorithm executes without errors but produces incorrect results for some nodes. It settles nodes too early because the greedy invariant assumes no future path can improve on the current distance. A negative edge can create exactly that situation, producing a shorter path through a node that was discovered later. The lack of any error message makes this a particularly dangerous mistake in interviews.
Was this helpful?