Graph Algorithm Playground, Explained

After reading this you will know how BFS, DFS, Dijkstra and Prim each walk a graph, what makes their answers correct, and how to read the frontier animation instead of just watching it.

What a graph algorithm actually does

A graph is a set of nodes joined by edges. In this playground each edge carries a weight, a positive number that stands for distance, cost or time. Every algorithm here starts at one node and spreads outward, but each one decides which node to visit next using a different rule. That single decision rule is the whole personality of the algorithm.

Take five cities laid out like this: node A connects to B (weight 4) and C (weight 1), C connects to B (weight 2) and D (weight 5), B connects to D (weight 1), and D connects to E (weight 3). Ask "how many hops from A to D?" and breadth-first search answers 2 (A to B to D, or A to C to D). Ask "what is the cheapest total weight from A to D?" and Dijkstra answers 4 (A to C is 1, C to B is 2, B to D is 1). Same graph, different questions, different tools.

When to reach for each of the four

The four algorithms split into two jobs. BFS and DFS explore. Dijkstra and Prim optimize.

Breadth-first search (BFS)
Visits nodes in order of hop count. Use it for shortest paths when every edge counts as 1, or to find the connected component containing a node.
Depth-first search (DFS)
Follows one path as far as it goes, then backtracks. Use it to detect cycles, find connected components, or produce a topological order. It does not find shortest paths.
Dijkstra's algorithm
Grows a shortest-path tree from the start, always expanding the closest unvisited node. Use it when edges have different non-negative weights.
Prim's algorithm
Builds a minimum spanning tree: the cheapest set of edges that connects every node. Use it when you want to connect everything, not travel from one point to another.

Dijkstra assumes every edge weight is zero or positive. A negative edge breaks the "closest node is final" guarantee, because a later detour could undercut a distance you already locked in. This playground only lets you enter non-negative weights for exactly that reason.

The one idea behind all four: a frontier and a key

Every algorithm here keeps a frontier: the set of discovered-but-not-yet-finalized nodes. At each step it removes the frontier node with the smallest key and finalizes it. The key is the only thing that changes between algorithms.

\text{key}(v) = \begin{cases} \text{insertion order} & \text{BFS (queue)} \\ \text{negative insertion order} & \text{DFS (stack)} \\ \text{dist}(u) + w(u,v) & \text{Dijkstra} \\ w(u,v) & \text{Prim} \end{cases}

Here u is the node being finalized, v is a neighbor still on the frontier, w(u,v) is the edge weight between them, and \text{dist}(u) is the best known total distance from the start to u. BFS and DFS ignore weight entirely: their key is just arrival order, first-in-first-out for BFS and last-in-first-out for DFS. Dijkstra ranks the frontier by total distance from the start. Prim ranks it by the weight of the single edge that would attach the node to the tree, ignoring how far that node is from the start.

That last contrast is the whole difference between Dijkstra and Prim. Dijkstra asks "how far is this node from the source?" Prim asks "how cheap is the wire that connects this node to what I have already built?"

Worked example on the demo graph

Dijkstra from A on the five-node graph

Use the demo edges: A to B (4), A to C (1), C to B (2), C to D (5), B to D (1), D to E (3). Start at A. The table tracks the best known distance to each node as nodes get finalized in order.

  1. Finalize A at distance 0. Relax its edges: B becomes 4, C becomes 1.
  2. Closest frontier node is C at 1. Finalize C. Relax: B via C is 1 + 2 = 3, which beats 4, so B drops to 3. D via C is 1 + 5 = 6.
  3. Closest is B at 3. Finalize B. Relax: D via B is 3 + 1 = 4, which beats 6, so D drops to 4.
  4. Closest is D at 4. Finalize D. Relax: E via D is 4 + 3 = 7.
  5. Finalize E at 7. Done.

Final shortest distances from A: A is 0, B is 3, C is 1, D is 4, E is 7. Notice B went from 4 to 3: the direct edge A to B was not the cheapest way to reach B. Watching this in the tool, the number over B updates the moment C is finalized.

Distance labels after each node is finalized (∞ means not yet reached)
FinalizedABCDE
A041∞∞
C0316∞
B0314∞
D03147
E03147

On the demo graph, Dijkstra from A selects edges A-C (1), C-B (2), B-D (1) and D-E (3), giving a shortest-path tree with total weight 7 and shortest distance 7 to E. Prim on the same graph selects A-C (1), C-B (2), B-D (1) and D-E (3), the same four edges here, with total tree weight 7. The trees coincide on this small graph, but adding an edge A-B of weight 2 splits them: Dijkstra keeps C-B (giving B distance 3), while Prim would still pick A-C then C-B since both cost the same, so try raising A-B to see them diverge.

BFS and DFS on the same graph

Ignore weights for a moment and count hops from A. BFS uses a queue, so it finishes A, then everything one hop away (B and C), then everything two hops away (D), then three hops (E). The visit order is A, B, C, D, E and the hop counts are 0, 1, 1, 2, 3. That matches Dijkstra only when you pretend every weight is 1.

DFS uses a stack, so it dives. From A it might go A, then B, then D, then E, then back up to try C. Visit order A, B, D, E, C. DFS reaches E in what looks like fewer stops, but the path it found (A to B to D to E) is a route, not a shortest route by weight. Do not read a DFS finish order as any kind of distance ranking.

BFS distance is measured in hops, not weight. Compare with the Dijkstra distances (0, 1, 3, 4, 7) to see how weighting reorders which node is "closest".

Reading the animation without fooling yourself

Three visual states matter. A node is unseen until an edge reaches it, then it joins the frontier, then it becomes finalized when the algorithm commits to it. The moment of finalization is the moment the distance label stops changing for Dijkstra.

The most instructive thing to watch is a label dropping. In the worked example B's label falls from 4 to 3 the instant C is finalized. That drop is a "relaxation": the algorithm found a cheaper route through a node it just committed to. If you never see a label drop, either your graph has no useful shortcuts or you are running BFS, which cannot relax because it does not track weight.

Run Dijkstra and Prim on the exact same graph back to back and compare the bold tree edges. When they pick different edges, hover the node where they disagree: Dijkstra kept the edge that shortens the path to the source, Prim kept the lighter edge regardless of source distance.

Common mistakes

Four errors trip up almost everyone the first time.

  • Expecting DFS to find shortest paths. It finds a path. On the demo graph DFS may report A to B to D to E when a shorter-weight route exists. Use BFS (unweighted) or Dijkstra (weighted) for shortest paths.
  • Confusing Prim's tree with Dijkstra's tree. They can look identical on small graphs, then diverge sharply when one long edge sits between two clusters. The playground footnote says it directly: they optimize different things.
  • Reading Prim's node labels as distances from the start. Prim labels the attaching edge weight, not the total path. A node three edges deep can carry a smaller label than its neighbor.
  • Assuming a shortest-path tree edge count equals the minimum. Dijkstra's tree is not the minimum spanning tree. On the demo graph both weigh 7, but that is coincidence, not a rule.

Related tools

If you want to see the same distance-first idea run inside a grid with obstacles, the Pathfinding Visualizer adds A* and greedy search to Dijkstra and BFS. For a different family of step-by-step algorithms, the Sorting Algorithm Visualizer races comparison sorts on the same array. Prim's greedy edge choice is the same instinct behind the tree in the Huffman Coding Visualizer, which repeatedly merges the two lightest items. To feel why O(V^2) versus O(E \log V) matters as graphs grow, try the Big-O Complexity Race.

Frequently asked questions

Why does Dijkstra visit nodes in a different order than BFS?

BFS orders by hop count, so it treats the weight-1 edge A to C and the weight-4 edge A to B as equal and visits B and C together. Dijkstra orders by total weight, so it finalizes C (distance 1) well before B (distance 3). Only when every edge weighs the same do the two orders agree.

Is Prim's algorithm just Dijkstra with a different key?

Structurally yes: both pull the smallest-key node off a frontier. The key differs. Dijkstra's key is \text{dist}(u) + w(u,v), the total distance from the source. Prim's key is w(u,v), the single attaching edge. That one change turns a shortest-path tree into a minimum spanning tree.

Can Dijkstra handle negative edge weights?

No. Once Dijkstra finalizes a node it never revisits it, which is only safe if no later path can be cheaper. A negative edge can create a cheaper path after finalization, breaking correctness. Algorithms like Bellman-Ford handle negatives at higher cost.

What does DFS actually solve well?

Cycle detection, connectivity, and ordering tasks like topological sort. DFS on the demo graph tells you all five nodes are reachable from A, and its back-edges reveal cycles. It is the wrong tool the moment you care about shortest distance.

Why do my Dijkstra and Prim trees sometimes match exactly?

On a small graph with few competing edges, the cheapest edge into a node and the edge on the shortest path to it are often the same edge. Add a heavy long-range edge or a cluster with an expensive bridge and the two trees separate.