Dijkstra's Algorithm Visualization — Step by Step

Finds the shortest path from a source to all other nodes in a weighted graph with non-negative edges.

GraphadvancedUpdated

// quick answer

Dijkstra's Algorithm is a graph algorithm that runs in O(V²) time on average with O(V) space. Finds the shortest path from a source to all other nodes in a weighted graph with non-negative edges.

How Dijkstra's Algorithm Works

Dijkstra's Algorithm finds the shortest path from a single source to all other vertices in a weighted graph. It greedily selects the unvisited vertex with the smallest distance, then relaxes its edges. Works only with non-negative edge weights. Time complexity is O(V²) with simple array, O((V+E) log V) with a binary heap.

Dijkstra's Algorithm Step by Step

The distance table

Dijkstra's algorithm maintains one central piece of state: a table of the best known distance from the start to every node, alongside a *previous node* pointer for path reconstruction. Take a five-node graph with weighted edges 0–1 (4), 0–2 (1), 1–2 (2), 1–3 (1), 2–3 (5), and 3–4 (3). Initially only the start is reachable for free: dist = [0, ∞, ∞, ∞, ∞]. The ∞ entries mean "no path found yet". The algorithm's whole job is to tighten this table until every entry is provably the true shortest distance.

Greedy selection: finalize the closest node

Each round picks the unfinalized node with the smallest known distance and locks it in. First round: node 0 wins with distance 0. Why is locking safe? Because every edge weight is non-negative, any other route to that node would have to travel through a node that is already *farther away* — it could never come back cheaper. This is the greedy insight the whole algorithm rests on, and precisely the step that breaks if negative edges exist. In the visualizer, selection and finalization appear as distinct steps, and finalized nodes never change again.

Relaxation: offering shorter routes

After finalizing a node, examine each edge leaving it and ask: does going *through this node* beat the neighbour's current best? Finalizing node 0 relaxes its edges: node 1 improves from ∞ to 0 + 4 = 4, node 2 from ∞ to 0 + 1 = 1. Table: [0, 4, 1, ∞, ∞]. Next round finalizes node 2 (distance 1, the smallest) and relaxes its edges: node 1 improves from 4 to 1 + 2 = 3 — the detour through 2 beats the direct edge — and node 3 gets 1 + 5 = 6. Table: [0, 3, 1, 6, ∞].

Finishing the trace

Round three finalizes node 1 (distance 3). Relaxing its edge to node 3 offers 3 + 1 = 4, beating 6 — node 3 improves again and its previous-pointer switches to node 1. Table: [0, 3, 1, 4, ∞]. Round four finalizes node 3 (distance 4) and relaxes 3–4: node 4 gets 4 + 3 = 7. Round five finalizes node 4; nothing is left. Final distances: [0, 3, 1, 4, 7]. Notice how node 3 was improved twice — early estimates are provisional, and only finalization makes a distance permanent.

Reconstructing the path

The distance table says node 4 costs 7, and the previous-pointers say how: 4 ← 3 ← 1 ← 2 ← 0. Reversed, the shortest path is 0 → 2 → 1 → 3 → 4 with weights 1 + 2 + 1 + 3 = 7. Compare the tempting direct-looking route 0 → 1 → 3 → 4: it costs 4 + 1 + 3 = 8. This is why BFS fails on weighted graphs — it would happily take the fewer-hop route and be wrong. Dijkstra counts *weight*, not hops, and provably minimizes it.

Complexity and the priority queue

The version visualized here scans the whole table for the minimum each round — O(V) per selection, O(V²) total, which is simple and perfectly fine for dense or small graphs. Production implementations store the frontier in a min-heap (priority queue), making each selection O(log V) and each relaxation O(log V), for O((V + E) log V) overall — a huge win on sparse graphs like road networks. Either way the answers are identical; only the cost of repeatedly asking "which node is closest?" changes.

Time & Space Complexity

Best CaseAverage CaseWorst CaseSpace
O(V+E)O(V²)O(V²)O(V)

Dijkstra's Algorithm Pseudocode

function dijkstra(graph, start):
  dist[start] = 0
  dist[all others] = infinity
  while unfinalized nodes:
    u = node with min dist
    finalize u
    for edge (u, v, weight):
      if dist[u] + weight < dist[v]:
        dist[v] = dist[u] + weight
        prev[v] = u

Press play in the visualizer above to watch each line execute with live highlighting.

Dijkstra's AlgorithmCode in JavaScript, Python & Java

JavaScript

// Uses an O(V) array scan for the minimum instead of a heap, for clarity.
function dijkstra(graph, start) {
  // graph: { node: [{ node, weight }, ...] }
  const nodes = Object.keys(graph);
  const dist = {};
  const prev = {};
  const finalized = new Set();

  for (const node of nodes) dist[node] = Infinity;
  dist[start] = 0;

  while (finalized.size < nodes.length) {
    let u = null; // closest unfinalized node
    for (const node of nodes) {
      if (!finalized.has(node) && (u === null || dist[node] < dist[u])) {
        u = node;
      }
    }
    if (u === null || dist[u] === Infinity) break; // rest unreachable
    finalized.add(u);

    for (const { node: v, weight } of graph[u]) {
      if (dist[u] + weight < dist[v]) { // relax
        dist[v] = dist[u] + weight;
        prev[v] = u;
      }
    }
  }
  return { dist, prev };
}

Python

def dijkstra(graph, start):
    """graph: dict mapping node -> list of (neighbor, weight) pairs.

    Uses an O(V) scan for the minimum instead of a heap, for clarity.
    """
    dist = {node: float('inf') for node in graph}
    prev = {}
    dist[start] = 0
    finalized = set()

    while len(finalized) < len(graph):
        u = min(
            (n for n in graph if n not in finalized),
            key=lambda n: dist[n],
            default=None,
        )
        if u is None or dist[u] == float('inf'):
            break  # remaining nodes are unreachable
        finalized.add(u)

        for v, weight in graph[u]:
            if dist[u] + weight < dist[v]:  # relax
                dist[v] = dist[u] + weight
                prev[v] = u

    return dist, prev

Java

import java.util.*;

public class Dijkstra {
    // Uses an O(V) scan for the minimum instead of a heap, for clarity.
    // graph[u] = list of {neighbor, weight} pairs.
    public static int[] dijkstra(int[][][] graph, int start) {
        int n = graph.length;
        int[] dist = new int[n];
        boolean[] finalized = new boolean[n];
        Arrays.fill(dist, Integer.MAX_VALUE);
        dist[start] = 0;

        for (int round = 0; round < n; round++) {
            int u = -1; // closest unfinalized node
            for (int v = 0; v < n; v++) {
                if (!finalized[v] && (u == -1 || dist[v] < dist[u])) u = v;
            }
            if (u == -1 || dist[u] == Integer.MAX_VALUE) break;
            finalized[u] = true;

            for (int[] edge : graph[u]) {
                int v = edge[0], w = edge[1];
                if (dist[u] + w < dist[v]) { // relax
                    dist[v] = dist[u] + w;
                }
            }
        }
        return dist;
    }
}

When to Use Dijkstra's Algorithm

  • Use it for shortest paths in weighted graphs with non-negative weights: road networks, flight costs, network routing (link-state protocols like OSPF are built on it).
  • Use it when you need one source to all destinations — a single run produces the entire distance table and shortest-path tree, not just one route.
  • Use the simple array-scan version for small or dense graphs and the heap-based version for large sparse ones — same algorithm, different bookkeeping cost.
  • Do not use it with negative edge weights: the greedy finalization step becomes unsound and can silently return wrong distances. Use the Bellman-Ford algorithm there.
  • Do not bother when all edges have equal weight — plain BFS finds the same shortest paths in O(V + E) with far less machinery.
  • For a single known destination on a huge graph, consider A* search — Dijkstra plus a heuristic that steers the search toward the goal instead of expanding uniformly in all directions.

Dijkstra's Algorithm — Frequently Asked Questions

Why can't Dijkstra's algorithm handle negative weights?

The algorithm permanently finalizes the closest unfinalized node each round, reasoning that no future route can beat it — every alternative would pass through something farther away. A negative edge breaks that logic: a path could go through a farther node and then come back cheaper via the negative edge, improving a distance that was already locked in. Bellman-Ford handles negative weights by re-relaxing all edges up to V-1 times instead of finalizing greedily.

What is the difference between Dijkstra's algorithm and BFS?

BFS minimizes the number of edges on a path and only works as a shortest-path algorithm when every edge costs the same. Dijkstra minimizes total edge weight, so it correctly prefers three cheap edges over one expensive one. Structurally they are cousins: BFS uses a plain queue because all frontier nodes are equally good, while Dijkstra needs a priority queue ordered by accumulated distance.

What is the time complexity of Dijkstra's algorithm?

It depends on how you find the closest unfinalized node. Scanning an array each round gives O(V squared), which is fine for small or dense graphs and is what this visualizer implements. A binary min-heap brings it to O((V + E) log V), a major improvement on sparse graphs where E is much smaller than V squared. A Fibonacci heap achieves O(E + V log V) in theory, though it is rarely used in practice.

Does Dijkstra's algorithm work on directed graphs?

Yes. Nothing in the algorithm assumes symmetric edges — it simply relaxes whatever outgoing edges each finalized node has, so one-way streets are handled naturally. An undirected graph is just the special case where every edge appears in both directions with the same weight. The only hard requirements are non-negative weights and a fixed source node.

When should I use A* instead of Dijkstra's algorithm?

When you want the shortest path to one specific destination and can estimate remaining distance — for example, straight-line distance on a map. A* adds that heuristic estimate to each node priority, steering the search toward the goal instead of expanding uniformly in every direction, and it explores far fewer nodes as a result. With a heuristic of zero, A* degrades exactly into Dijkstra. For one-source-to-all-destinations tables, plain Dijkstra remains the right choice.

Compare Dijkstra's Algorithm

Related Algorithms

References & Further Reading