Floyd-Warshall
Find the cheapest path between every pair of nodes at once, by letting each node in turn act as a possible stop in the middle.
O(n³)Space O(n²)2 practice problemsStep-through pictureThe idea
Sometimes you need the cheapest cost between every pair of nodes, not just from one start: a full table of travel times between all offices, for example. Floyd-Warshall builds that table with three nested loops.
It starts with a table dist where dist[i][j] is the cost of a direct edge from i to j, 0 when i === j, and Infinity otherwise. Then it considers each node mid in turn and asks, for every pair: is it cheaper to go from i to mid and then from mid to j?
After mid has gone through every node, the table holds the cheapest cost between every pair. It works with negative edges too, as long as there is no negative cycle. The cost is n³ steps, so it fits graphs of up to a few hundred nodes.
An everyday example
Adding layover cities one at a time
You have a table of direct flight prices between cities. First you allow layovers only in Paris: for every pair of cities, check whether flying through Paris is cheaper and update the table. Then you also allow layovers in Rome, using the already-updated table, so routes through both Paris and Rome are covered. After you have allowed every city, the table has the best price for any trip with any layovers.
| In the example | In the code |
|---|---|
| The table of prices | dist[i][j] |
| The city newly allowed as a layover | mid |
Flying from i to the layover, then on to j | dist[i][mid] + dist[mid][j] |
| Updating the table with a cheaper route | dist[i][j] = dist[i][mid] + dist[mid][j] |
From the slow way to floyd-warshall
- Start with the obvious way: run a one-start algorithm from every node. Dijkstra from each of
nstarts costsO(n · e log n)but can't handle negative edges. Bellman-Ford from each start handles them, but on a dense graph that's aboutn⁴steps: 100 million for just 100 nodes. - Look at the cheapest paths in a different way: by which nodes they're allowed to stop at in the middle. With no middle stops allowed, the only paths are direct edges, and that's just the edge table. Now allow one more node,
k, as a stop. A cheapest path fromitojeither doesn't usek, so it's the answer we already had, or it goes throughkonce. - If it goes through
k, it splits into two halves,itokandktoj, and each half only stops at nodes allowed beforek. We already know the best cost of both halves. So allowing one new stop needs just one check per pair: isdist[i][k] + dist[k][j]smaller thandist[i][j]? That's a dynamic programming step, filling a table one allowed stop at a time. - One more fact keeps the memory small. While
kis the new stop, rowkand columnkcan't improve, because going fromitokthroughkaddsdist[k][k] = 0. So we can update a singlenbyntable in place instead of keeping a new table for everyk. Three nested loops withmidon the outside: that is Floyd-Warshall.
How it works
- Make an
nbyntabledist:0on the diagonal,Infinityeverywhere else. - For each edge
[from, to, weight], setdist[from][to]to the smaller of its current value andweight. - For each
midfrom0ton - 1, go through every pairi,j. - If
dist[i][mid] + dist[mid][j]is less thandist[i][j], updatedist[i][j]. - After the last
mid,dist[i][j]is the cheapest cost fromitoj, orInfinityif there's no path.
Watch it run
This picture runs the code below on a real input. Press Step to move one step at a time, or Play to watch. The highlighted line is the one that just ran. Change the input to see other cases.
See every step at onceThe same run, laid out side by side
The code
Point at a note to light up the lines it explains.
function floydWarshall(n, edges) {const dist = Array.from({ length: n }, (_, i) =>Array.from({ length: n }, (_, j) => (i === j ? 0 : Infinity)),);for (const [from, to, weight] of edges) {dist[from][to] = Math.min(dist[from][to], weight);}for (let mid = 0; mid < n; mid++) {for (let i = 0; i < n; i++) {for (let j = 0; j < n; j++) {if (dist[i][mid] + dist[mid][j] < dist[i][j]) {dist[i][j] = dist[i][mid] + dist[mid][j];}}}}return dist;}
How fast it is
| Time | O(n³) |
|---|---|
| Extra memory | O(n²) |
Three nested loops each run n times. The table has one cell for every pair of nodes.
- Best case
O(n³)Every input, even a graph with no edges: the three loops always run
n × n × nchecks. - Average case
O(n³)Every input; the number of updates changes, but the number of checks doesn't.
- Worst case
O(n³)Every input. For
n = 400that's 64 million checks, which is about the practical limit.
Common mistakes
- Putting the
midloop inside theiandjloops. The answer is then wrong for paths with several stops. - Forgetting
dist[i][i] = 0, so a node seems unable to reach itself. - Overwriting a direct edge with a more expensive duplicate edge instead of keeping the minimum.
- Using a large number like
1e9instead ofInfinityand then adding two of them together, which can look like a real path.
When to use it
Look for these clues in a problem:
- The question needs the cheapest cost between many pairs, or every pair.
nis small, about 400 or less, son³steps are fine.- You need to know which nodes can reach which (a reachability table).
- Edge costs may be negative, but there is no loop with a negative total.
Check yourself
Answer each one in your head first, then open it.
What goes wrong if mid is the innermost loop?
Each pair i, j is finished before the halves it needs have been improved, so paths with more than one stop are missed.
Why is it safe to update the table in place during a round?
The cells read in round mid, dist[i][mid] and dist[mid][j], can't change in that round, because improving them would add dist[mid][mid] = 0.
How many checks does it make for 50 nodes?
50 × 50 × 50 = 125,000, whatever the edges are.
In the trace, why did dist[2][1] become 10 only in the last round?
Its cheapest path is 2 → 3 → 0 → 1, which stops at node 3. Node 3 is only allowed as a stop in the last round, mid = 3.
Practice problems
Stuck on one? Tuto is in the playground too, and gives you hints, never the answer.
0 of 2 solved. Solve one to earn the bronze medal, half for silver, and all of them for gold.
Start practicingGo further
You don't need these to start practicing. Open one when you want more.
Why it works
The reasoning that makes it always right
The rule: after the outer loop finishes mid = k, dist[i][j] is the cheapest cost of a path from i to j whose middle stops are all among nodes 0 to k. Before the first round, no middle stops are allowed, and the table holds exactly the direct edges, with 0 on the diagonal.
Each round keeps the rule. A cheapest path that may stop at 0 to k either avoids k, and its cost is already in the table, or passes through k exactly once, since without negative loops repeating k never helps. Then its two halves only stop at 0 to k - 1, so their costs are dist[i][k] and dist[k][j]. Updating in place is safe, because during round k those two cells can't change: improving them would mean adding dist[k][k] = 0.
The loops always end after n³ checks. After the last round, every node is allowed as a stop, so the table holds the true cheapest costs. If there is a negative loop, some node on it ends with dist[i][i] < 0, because going around the loop from i back to i costs less than staying put.
Going deeper
3 more ideas
Why the order of the loops matters
After the outer loop finishes mid = k, dist[i][j] is the cheapest path from i to j that only stops at nodes 0 to k along the way. Moving to k + 1, a cheapest path either doesn't use node k + 1 (already correct) or goes i → k + 1 → j, and both halves only use stops up to k (also already correct). That's why mid must be the outer loop.
Cost and when to use something else
Three nested loops of n make n³ steps: 1 million for n = 100, 64 million for n = 400. If the graph is large but sparse and you need only a few starts, running Dijkstra from each start is cheaper. Floyd-Warshall wins on small, dense graphs and for its short code.
Negative cycles and reachability
If any dist[i][i] ends up below 0, node i is on a negative cycle. For a plain "can i reach j?" table, use booleans instead of numbers: reach[i][j] = reach[i][j] || (reach[i][mid] && reach[mid][j]).
Work it by hand
Every step in a table, with why
Four nodes with one negative edge and a duplicate edge. Every update the code makes is one row; the other 56 of the 64 checks change nothing.
n = 4, edges = [[0,1,5], [1,2,-2], [2,3,4], [3,0,1], [0,3,9], [3,1,8], [0,3,12]]
| Step | mid | Pair i → j | Old dist[i][j] | dist[i][mid] + dist[mid][j] | What happens | Why |
|---|---|---|---|---|---|---|
| 1 | - | all edges | - | - | Load the table | Rows start as [0, 5, ∞, 9], [∞, 0, -2, ∞], [∞, ∞, 0, 4], [1, 8, ∞, 0]. The two edges 0 → 3 cost 9 and 12, and Math.min keeps 9. |
| 2 | 0 | 3 → 1 | 8 | 1 + 5 = 6 | Update to 6 | Allowing node 0 as a stop, 3 → 0 → 1 costs 6, cheaper than the direct edge of 8. No other pair improves through node 0. |
| 3 | 1 | 0 → 2 | ∞ | 5 + (-2) = 3 | Update to 3 | Through node 1, node 0 reaches node 2 for the first time, helped by the negative edge. |
| 4 | 1 | 3 → 2 | ∞ | 6 + (-2) = 4 | Update to 4 | This uses dist[3][1] = 6, found in the mid = 0 round. So the real path is 3 → 0 → 1 → 2, with two middle stops. |
| 5 | 2 | 0 → 3 | 9 | 3 + 4 = 7 | Update to 7 | Going 0 → 1 → 2 → 3 costs 7, beating the direct edge of 9. |
| 6 | 2 | 1 → 3 | ∞ | -2 + 4 = 2 | Update to 2 | Node 1 reaches node 3 through node 2. |
| 7 | 3 | 1 → 0 | ∞ | 2 + 1 = 3 | Update to 3 | Now node 3 is allowed too, so every path is possible. 1 → 2 → 3 → 0 costs 3. |
| 8 | 3 | 2 → 0 | ∞ | 4 + 1 = 5 | Update to 5 | Node 2 reaches node 0 through node 3. |
| 9 | 3 | 2 → 1 | ∞ | 4 + 6 = 10 | Update to 10 | dist[3][1] = 6 already includes the stop at node 0, so this is the path 2 → 3 → 0 → 1. |
The final table is [0, 5, 3, 7], [3, 0, -2, 2], [5, 10, 0, 4], [1, 6, 4, 0]. The loops always make 4 × 4 × 4 = 64 checks; 8 of them updated a cell. Every dist[i][i] is still 0, so there is no negative loop.
Compared with other ways
When to pick something else
| Approach | Time | Extra memory | Choose it when |
|---|---|---|---|
| Floyd-Warshall | O(n³) | O(n²) | You need every pair, n is at most a few hundred, and you want short code that handles negative edges. |
| Dijkstra from every node | O(n · e log n) | O(n + e) | The graph is large and sparse with no negative edges, or you only need a few starts. |
| Bellman-Ford from every node | O(n² · e) | O(n) | Almost never for all pairs; it's slower than Floyd-Warshall on dense graphs and Johnson's on sparse ones. |
Extra memory: O(n²): one n by n table, updated in place. Keeping a separate table for every mid would cost O(n³).
How the steps grow
- Floyd-Warshall, Bellman-Ford from every node
O(n³) - Dijkstra from every node
O(n · e log n)
| Approach | n = 10 | n = 1,000 | n = 1,000,000 |
|---|---|---|---|
| Floyd-Warshall | 1,000 | 1 billion | more than a quadrillion |
| Dijkstra from every node | 332 | 10 million | 20 trillion |
| Bellman-Ford from every node | 1,000 | 1 billion | more than a quadrillion |
Inputs that need care
5 tricky inputs
- No path from
itoj dist[i][j]staysInfinity.Infinityplus any number is stillInfinity, so a missing half never wins a comparison.- Two edges for the same pair, like
0 → 3costing 9 and 12 Math.minon line 6 keeps the cheaper one. Plain assignment would let the later, more expensive edge overwrite it.- A negative edge with no negative loop, like
1 → 2costing -2 - It works as is, as the trace shows.
- A negative loop, or a negative self-loop like
[2, 2, -1] - Some
dist[i][i]ends below 0. Check the diagonal after the loops; costs between pairs that can use the loop are meaningless. - An undirected graph
- Add each edge in both directions. A negative undirected edge is itself a negative loop.
Variations and follow-ups
Twists interviewers add
Rebuilding the paths
Keep a second table next, where next[i][j] starts as j for every edge. When dist[i][j] improves through mid, set next[i][j] = next[i][mid]. To list the path, start at i and keep jumping to next[current][j] until you reach j.
Widest paths
Replace + with Math.min and < with > to find, for every pair, the path whose narrowest edge is as wide as possible, such as the best bandwidth between servers. Start missing pairs at 0 instead of Infinity; the same mid argument applies.
Adding one edge later
If the table is already finished and a new edge u → v with cost w appears, you don't need to run it again. For every pair, try dist[i][u] + w + dist[v][j]. That's O(n²) per new edge.
Johnson's algorithm for big sparse graphs
Run Bellman-Ford once to give each node a potential, use it to turn every edge cost into a non-negative one, then run Dijkstra from every node. It finds all pairs in about O(n · e log n), which beats n³ when edges are few.
In an interview
Spot it, explain it, answer follow-ups
- Spot it from "between every pair", "for many queries of distance from
atob", or "which cities can reach which", withnup to about 400. - Explain the loop order out loud: "after round
mid, the table is right for paths that only stop at nodes0tomid". That sentence answers the most common question, whymidmust be the outer loop. - Test by hand a three-node chain where the only path from the first to the last needs a stop in the middle, and check that the cell fills in during the right round.
- Expect "how would you detect a negative loop?". Answer: after the loops, any
dist[i][i] < 0means nodeiis on one.
Where frontend code uses it
The same idea in real UI code
- Dashboards that show a full table of latencies or hop counts between every pair of services or regions.
- Design tools that precompute which components can reach which through nested references, so a "used by" panel can answer any pair instantly.
- Small games and puzzle apps that precompute distances between all key spots on a map once, then answer many queries during play.