Dijkstra's algorithm
Shortest paths with non-negative weights: always settle the cheapest unsettled node, because nothing can ever undercut it. Greedy, and provably right.
- Time:
- O((V + E) log V)
- Space:
- O(V)
The problem it solves
The moment edges get weights, BFS’s promise collapses: fewest edges no longer means lowest cost. A two-hop route of 3 + 4 beats a direct road of 10, and route planners, network packet routing, and every “cheapest way from A to everything” question live in this weighted world. Dijkstra’s algorithm computes lowest-cost paths from one source to all nodes, with one hard precondition that everything else depends on: no negative edge weights.
It earns its place in interviews twice over: directly, as the canonical weighted-shortest-path question, and indirectly, because A* — the game-industry and robotics standard — is exactly Dijkstra with a hint added, and Bellman-Ford is exactly Dijkstra with the greed removed. Understand this one and its neighbours come almost free.
The intuition — and where it breaks down
Water spreading through pipes from one source, at constant speed. The wet frontier reaches junctions in order of their true distance — a junction 7 metres of pipe away gets wet at time 7, full stop, no matter how many pipes lead there. When the water first arrives somewhere, no later arrival can beat it, because any other route flows through junctions that got wet earlier and still had farther to travel.
Dijkstra discretizes the water. Each node holds a tentative distance — the cheapest route found so far — and the algorithm repeatedly makes the boldest legal claim: the unsettled node with the smallest tentative distance is done; its number is final. That claim is safe for exactly the water reason: any hypothetical better route would have to exit through some other unsettled node, whose tentative distance is already ≥ this one, plus more pipe on top. Non-negative pipe. That’s the proof, whole.
And that’s precisely where the analogy breaks: a negative edge is pipe that flows backwards in time. Water cannot arrive before it left, but a cost can shrink en route — and then the “first arrival is final” claim is simply false. Settled nodes would need revisiting; Dijkstra never revisits; wrong answers come out with full confidence. Negative edges don’t slow Dijkstra down, they break its theorem — that’s Bellman-Ford’s territory.
A walkthrough you can check
Graph: A→B (1), A→C (4), B→C (2), C→D (1), B→D (6). Source A.
Asettles at 0. Relax its edges:Bgets tentative 1,Cgets 4.- Smallest unsettled:
B(1). Settle it. RelaxB→C: 1 + 2 = 3 beats 4 —C’s label drops to 3. RelaxB→D: tentative 7. - Smallest unsettled:
C(3). Settle. RelaxC→D: 3 + 1 = 4 beats 7 —Ddrops to 4. - Settle
D(4). Done: distances 0, 1, 3, 4.
Two things in that run deserve the stare. First, C’s label changed — tentative numbers are working hypotheses, and the direct road A→C lost to a detour. Second, D’s first offer (7, via B) was beaten before D settled — improvements are only impossible after settling, which is the entire content of the algorithm’s guarantee. The prediction prompts in the visualization target both moments: “which node settles next?” and “does this relaxation improve the label?”.
The invariant
Settled distances are exact, and they leave the priority queue in non-decreasing order. The second half is the checkable version of the first: if a settled value ever came out smaller than a previously settled one, the proof has been violated (or an edge was negative). The trace asserts this ordering as a unit test — the invariant is machine-checked, not just recited.
For the interview, be able to derive rather than recite: suppose node u settles at distance d, but a cheaper path exists. That path leaves the settled region through some unsettled node v with tentative t ≥ d (else v would have settled first), then travels edges summing to ≥ 0. Total ≥ d. Contradiction — and the ≥ 0 step is the exact point a negative edge punctures.
Complexity, derived
Every node settles once: V pops. Every edge relaxes once, when its source settles: E relaxations, each possibly pushing a heap entry. With a binary heap, pushes and pops cost O(log (heap size)), heap size at most E — total O((V + E) log V) for the connected case. The implementation here uses lazy deletion: rather than decrease-key surgery, an improved node is simply pushed again, and stale entries are skipped at pop time — the trace narrates the skip when it happens, because seeing one stale entry surface teaches the technique better than a paragraph. Cost of laziness: a fatter heap, same asymptotics, dramatically simpler code — which is why real implementations overwhelmingly choose it.
The degenerate cases anchor the picture: all weights equal → Dijkstra visits in BFS order and the heap was overhead (use BFS); weights in {0, 1} → a deque suffices (0-1 BFS); dense graph with V² edges → the ancient no-heap O(V²) scan is actually optimal. Knowing when not to pay for the heap is part of knowing the algorithm.
What people get wrong
Running it with negative weights anyway. The failure is silent — plausible-looking wrong distances, no error. If negatives are possible: Bellman-Ford (O(VE), also detects negative cycles), or for the all-pairs version, Johnson’s reweighting trick. Saying those names at the right moment is the follow-up answer.
Settling on push instead of pop. Marking a node “done” when it first enters the queue uses its tentative (possibly improvable) value as final — the walkthrough’s step 3 is a direct counterexample, since D entered at 7 and settled at 4. Done means popped with the smallest label, never merely seen.
Skipping the stale-entry check with lazy deletion. Without if settled(node): continue at pop, a node processes twice with different distances, corrupting downstream relaxations. The check is two lines and non-optional.
Reconstructing paths by re-searching. Store a predecessor pointer at each successful relaxation; the path is a backwards walk. Recomputing routes afterwards is quadratic silliness under pressure.
Implementation notes across languages
Python: heapq + tuples (distance, node) — distance first, so tuple ordering does the comparator’s job; push duplicates, skip stale. Java: PriorityQueue has no decrease-key, which makes lazy deletion not just easier but essentially mandatory; entries as int[]{dist, node} with a comparator, or small records. C++: std::priority_queue is a max-heap — the classic trap; use greater<> or negate distances. All three: the visited/settled structure is a boolean array, and the distance array initializes to infinity except source zero. And in all three, A* is a ten-line diff from what you just wrote — priority becomes dist + heuristic(node) — which is the cheapest “senior” flourish available in this corner of the interview map.
Why this visualization
Flat first: distance labels, the dashed frontier and the red settling node carry the algorithm, and the priority queue is drawn beside the graph with its cheapest entry at the front. Edge-weight comparisons are easier to judge without perspective.
When to reach for it
Single-source shortest paths with non-negative weights. Negative edges break the settling argument — that is Bellman-Ford. All-equal weights degrade it to BFS, which is cheaper. Grids with terrain costs, network latencies, cheapest-flight problems with a twist.
The follow-up questions
What interviewers ask after "implement dijkstra's algorithm" — with answers.
- Why exactly does a negative edge break it?
- The proof that the cheapest unsettled node is final assumes extending a path never shortens it. A negative edge can undercut an already-settled node, and Dijkstra never revisits settled nodes.
- What is the lazy-deletion trick the heap version uses?
- Instead of decrease-key, push a new entry every time a distance improves and skip entries that are stale when popped. Simpler, same asymptotics with a log factor on the bigger heap.
- How does A* relate?
- A* is Dijkstra ordered by dist + heuristic. With an admissible heuristic it explores a subset of what Dijkstra would, and with h = 0 it is Dijkstra exactly.
Where it goes wrong
- Running it with negative weights and trusting the output.
- Settling a node on push rather than on pop, which breaks the greedy proof.
- Forgetting to skip stale heap entries, which is harmless for correctness but a classic source of confusion when tracing by hand.
Test yourself
18 interview questions on dijkstra's algorithm — complexity, trade-offs, edge cases and invariants — as flip cards or a scored quiz, with the answers linking back to the exact step of the trace above.
Problems built on this pattern
- Network Delay Time
- Path With Minimum Effort
- Cheapest Flights Within K Stops
- Swim in Rising Water
Related algorithms
- Breadth-first searchExplores a graph in rings of increasing distance.
- A* searchDijkstra with a compass: the heap is ordered by cost so far PLUS a never-overestimating guess of the cost remaining.
- Bellman–FordShortest paths with negative edges allowed: relax every edge, V−1 times, and let a fixed point — or a negative cycle — announce itself.
- KruskalMinimum spanning tree by global greed: consider edges lightest-first, accept each unless it would close a cycle.