DSAFloyd-Warshall Algorithm

Floyd-Warshall Algorithm

The Floyd-Warshall algorithm solves the all-pairs shortest path problem — it finds the shortest path between every pair of vertices in a weighted graph in a single run. While Dijkstra's computes shortest paths from one source, Floyd-Warshall gives you the complete distance matrix for all V² pairs at once.

Note
Floyd-Warshall works on both directed and undirected graphs, handles negative edge weights (unlike Dijkstra's), and can detect negative cycles. Time: O(V³). Space: O(V²).
The Key Insight: Intermediate Vertices

The algorithm's brilliance lies in a simple question: can we improve the path from i to j by routing through some intermediate vertex k? It iterates over every possible intermediate vertex k and asks: is the path i → k → j shorter than the best known direct path i → j? If yes, update the distance.

Core recurrence (applied for each intermediate vertex k):

  dist[i][j] = min(dist[i][j],  dist[i][k] + dist[k][j])
                    ↑                  ↑              ↑
             current best       best path       best path
             i → j             i → k           k → j

  After processing all k from 0 to V-1, dist[i][j] holds
  the true shortest path between every pair (i, j).
Initialization Rules

Before the main triple loop starts, the distance matrix is initialized from the graph's edge weights:

  • dist[i][i] = 0 — the distance from any vertex to itself is zero

  • dist[i][j] = weight(i, j) — if a direct edge exists between i and j

  • dist[i][j] = Infinity — if there is no direct edge from i to j

Example graph (4 vertices: 0, 1, 2, 3):

        5
  (0)-------(1)
   |  \       |
  1|   3\     |2
   |     \    |
  (2)-------(3)
        4

  Edges: 0→1 (5), 0→2 (1), 0→3 (3), 1→3 (2), 2→3 (4)

  Initial dist matrix:
       0     1     2     3
  0  [ 0,    5,    1,    3  ]
  1  [ ∞,    0,    ∞,    2  ]
  2  [ ∞,    ∞,    0,    4  ]
  3  [ ∞,    ∞,    ∞,    0  ]
Step-by-Step Walkthrough

Let's trace through the algorithm on the 4-vertex graph above. We run three nested loops: for k (intermediate), for i (source), for j (destination).

=== k = 0 (route through vertex 0) ===

  Check all pairs i,j: can we do better via vertex 0?

  i=1, j=2: dist[1][2] = min(∞, dist[1][0] + dist[0][2]) = min(∞, ∞+1) = ∞  (no change)
  i=1, j=3: dist[1][3] = min(2, dist[1][0] + dist[0][3]) = min(2, ∞)    = 2  (no change)
  i=2, j=1: dist[2][1] = min(∞, dist[2][0] + dist[0][1]) = min(∞, ∞+5)  = ∞  (no change)
  i=2, j=3: dist[2][3] = min(4, dist[2][0] + dist[0][3]) = min(4, ∞)    = 4  (no change)

  Matrix after k=0: unchanged.

=== k = 1 (route through vertex 1) ===

  i=0, j=3: dist[0][3] = min(3, dist[0][1] + dist[1][3]) = min(3, 5+2)  = 3  (no change)
  i=2, j=3: dist[2][3] = min(4, dist[2][1] + dist[1][3]) = min(4, ∞)    = 4  (no change)

  Matrix after k=1: unchanged.

=== k = 2 (route through vertex 2) ===

  i=0, j=3: dist[0][3] = min(3, dist[0][2] + dist[2][3]) = min(3, 1+4)  = 3  (no change)

  Matrix after k=2: unchanged.

=== k = 3 (route through vertex 3) ===

  (no outgoing edges from 3 in this graph)

  Final dist matrix:
       0     1     2     3
  0  [ 0,    5,    1,    3  ]
  1  [ ∞,    0,    ∞,    2  ]
  2  [ ∞,    ∞,    0,    4  ]
  3  [ ∞,    ∞,    ∞,    0  ]

  Shortest path 0→3: direct edge weight 3.
  Shortest path 0→1: direct edge weight 5 (no shorter route exists).
  Shortest path 2→3: direct edge weight 4.
Tip
Notice that Floyd-Warshall naturally handles directed graphs. In an undirected graph, add both directions (i→j and j→i) during initialization and the algorithm works identically.
Full JavaScript Implementation

JS
/**
 * Floyd-Warshall: all-pairs shortest paths
 *
 * @param {number} n       - number of vertices (0-indexed: 0..n-1)
 * @param {number[][]} edges - [from, to, weight]
 * @returns {{ dist: number[][], next: (number|null)[][] }}
 *   dist[i][j] = shortest distance from i to j (Infinity if unreachable)
 *   next[i][j] = first step on the shortest path from i to j (for reconstruction)
 */
function floydWarshall(n, edges) {
  const INF = Infinity;

  // 1. Initialize distance matrix
  const dist = Array.from({ length: n }, (_, i) =>
    Array.from({ length: n }, (_, j) => (i === j ? 0 : INF))
  );

  // 2. Initialize "next" matrix for path reconstruction
  //    next[i][j] = the vertex after i on the shortest path to j
  const next = Array.from({ length: n }, () => Array(n).fill(null));

  // 3. Populate direct edges
  for (const [u, v, w] of edges) {
    if (w < dist[u][v]) {        // handle parallel edges (keep smallest)
      dist[u][v] = w;
      next[u][v] = v;
    }
  }

  // Self-loops: we go "nowhere" on a zero-length path
  for (let i = 0; i < n; i++) next[i][i] = i;

  // 4. Main triple loop — the heart of Floyd-Warshall
  for (let k = 0; k < n; k++) {        // intermediate vertex
    for (let i = 0; i < n; i++) {      // source
      for (let j = 0; j < n; j++) {    // destination
        if (dist[i][k] === INF || dist[k][j] === INF) continue; // avoid Inf + Inf
        const through = dist[i][k] + dist[k][j];
        if (through < dist[i][j]) {
          dist[i][j] = through;
          next[i][j] = next[i][k];     // go to k first, then on to j
        }
      }
    }
  }

  return { dist, next };
}

// --- Path Reconstruction ---
function getPath(next, u, v) {
  if (next[u][v] === null) return [];  // unreachable
  const path = [u];
  while (u !== v) {
    u = next[u][v];
    path.push(u);
  }
  return path;
}

// --- Example usage ---
const n = 4;
const edges = [
  [0, 1, 5],
  [0, 2, 1],
  [0, 3, 3],
  [1, 3, 2],
  [2, 3, 4],
];

const { dist, next } = floydWarshall(n, edges);

console.log("Shortest distances:");
for (let i = 0; i < n; i++) {
  console.log(`  From ${i}:`, dist[i].map(d => d === Infinity ? "∞" : d));
}

console.log("\nPath from 0 to 3:", getPath(next, 0, 3)); // [0, 3]
console.log("Path from 2 to 1:", getPath(next, 2, 1)); // [] (unreachable in directed graph)
Detecting Negative Cycles

A negative cycle is a cycle whose total edge weight is negative. If a negative cycle is reachable between i and j, the "shortest" path is −∞ (you can loop forever to decrease the cost infinitely). Floyd-Warshall detects this elegantly.

After running Floyd-Warshall:

  If dist[i][i] < 0 for any vertex i,
  then vertex i is part of a negative cycle.

  More precisely: if dist[i][i] < 0 after the algorithm,
  the "shortest path" from i back to itself is negative —
  meaning you can keep looping around that cycle to
  reduce any path that passes through i to -∞.

JS
function hasNegativeCycle(dist, n) {
  for (let i = 0; i < n; i++) {
    if (dist[i][i] < 0) return true;
  }
  return false;
}

// Any path through a vertex v in a negative cycle is also -∞
function isDistInfinitelyNegative(dist, n, u, v) {
  for (let k = 0; k < n; k++) {
    // If k is in a negative cycle AND is reachable from u AND can reach v
    if (dist[k][k] < 0 && dist[u][k] !== Infinity && dist[k][v] !== Infinity) {
      return true;
    }
  }
  return false;
}

// Graph with negative cycle: 0→1 (1), 1→2 (-3), 2→1 (1)
// Cycle 1→2→1 has total weight -3+1 = -2 (negative!)
const edgesWithNegCycle = [
  [0, 1, 1],
  [1, 2, -3],
  [2, 1, 1],   // forms cycle 1→2→1 with weight -3+1 = -2
];

const { dist: d2 } = floydWarshall(3, edgesWithNegCycle);
console.log("Negative cycle?", hasNegativeCycle(d2, 3)); // true
console.log("dist[1][1] =", d2[1][1]); // < 0
Warning
Floyd-Warshall does NOT correctly compute shortest paths when a negative cycle exists — the distances for vertices reachable through that cycle become meaningless. Always check for negative cycles before trusting the result.
Transitive Closure

The transitive closure of a graph is a matrix reach[i][j] that is true if vertex j is reachable from vertex i (by any path, regardless of weight). Floyd-Warshall can compute this directly by replacing addition with logical OR and min with logical AND.

JS
/**
 * Compute transitive closure: reach[i][j] = true if j is reachable from i
 */
function transitiveClosure(n, edges) {
  // Initialize: reach[i][j] = true if direct edge i→j exists, or i === j
  const reach = Array.from({ length: n }, (_, i) =>
    Array.from({ length: n }, (_, j) => i === j)
  );

  for (const [u, v] of edges) {
    reach[u][v] = true;
  }

  // Floyd-Warshall variant: use OR instead of min
  for (let k = 0; k < n; k++) {
    for (let i = 0; i < n; i++) {
      for (let j = 0; j < n; j++) {
        reach[i][j] = reach[i][j] || (reach[i][k] && reach[k][j]);
      }
    }
  }

  return reach;
}

const edges = [[0, 1], [1, 2], [2, 3]];
const reach = transitiveClosure(4, edges);

console.log("0 can reach 3?", reach[0][3]); // true  (0→1→2→3)
console.log("3 can reach 0?", reach[3][0]); // false (no back edges)
console.log("2 can reach 1?", reach[2][1]); // false
Complexity Analysis

Aspect

Value

Explanation

Time complexity

O(V³)

Three nested loops, each 0..V-1

Space complexity

O(V²)

dist[][] and next[][] matrices, each V×V

Handles negative weights

Yes

Unlike Dijkstra's algorithm

Handles negative cycles

Detects only

Distances become incorrect; check dist[i][i]

Works on disconnected graphs

Yes

Unreachable pairs stay at Infinity

Works on undirected graphs

Yes

Initialize both directions of each edge

Floyd-Warshall vs Other Shortest Path Algorithms

Algorithm

Source

Negative weights

Negative cycles

Time

Best for

Dijkstra's

Single source

No

No

O((V+E) log V)

Sparse graphs, one source

Bellman-Ford

Single source

Yes

Detects

O(VE)

Negative weights, one source

Floyd-Warshall

All pairs

Yes

Detects

O(V³)

Dense graphs, all pairs

Johnson's

All pairs

Yes

No

O(VE + V² log V)

Sparse graphs, all pairs

Tip
Use Floyd-Warshall when V is small (under ~400) and you need distances between all pairs. For large sparse graphs where you only need one source, use Dijkstra's or Bellman-Ford.
Problem 1: Find the City With the Smallest Number of Neighbors (LeetCode 1334)

Given n cities and edges with distances, find the city that has the fewest number of cities reachable within a distance threshold. Break ties by returning the city with the greater index. This is a textbook Floyd-Warshall application: compute all-pairs shortest paths, then count reachable cities per source.

JS
/**
 * LeetCode 1334 — Find the City With the Smallest Number of Neighbors
 * at a Threshold Distance
 *
 * @param {number} n
 * @param {number[][]} edges  - [from, to, weight]
 * @param {number} distanceThreshold
 * @return {number}
 */
var findTheCity = function(n, edges, distanceThreshold) {
  const INF = Infinity;

  // 1. Initialize distance matrix
  const dist = Array.from({ length: n }, (_, i) =>
    Array.from({ length: n }, (_, j) => (i === j ? 0 : INF))
  );

  // 2. Fill direct edges (undirected)
  for (const [u, v, w] of edges) {
    dist[u][v] = Math.min(dist[u][v], w);
    dist[v][u] = Math.min(dist[v][u], w); // undirected
  }

  // 3. Floyd-Warshall
  for (let k = 0; k < n; k++) {
    for (let i = 0; i < n; i++) {
      for (let j = 0; j < n; j++) {
        if (dist[i][k] !== INF && dist[k][j] !== INF) {
          dist[i][j] = Math.min(dist[i][j], dist[i][k] + dist[k][j]);
        }
      }
    }
  }

  // 4. For each city, count how many other cities are within threshold
  let bestCity = -1;
  let bestCount = Infinity;

  for (let i = 0; i < n; i++) {
    let count = 0;
    for (let j = 0; j < n; j++) {
      if (i !== j && dist[i][j] <= distanceThreshold) {
        count++;
      }
    }
    // Keep city with fewest neighbors; on tie, prefer higher index
    if (count <= bestCount) {
      bestCount = count;
      bestCity = i;
    }
  }

  return bestCity;
};

// Example: n=4, edges=[[0,1,3],[1,2,1],[1,3,4],[2,3,1]], threshold=4
// Expected: 3
console.log(findTheCity(4, [[0,1,3],[1,2,1],[1,3,4],[2,3,1]], 4)); // 3
Problem 2: Network Delay Time — All-Pairs Version (LeetCode 743)

The original problem asks for the minimum time for a signal to reach all nodes from a single source k. Using Floyd-Warshall, we can answer this for every possible source in one shot, which is useful when the source may change at query time.

JS
/**
 * Network Delay Time — answered for ANY source using Floyd-Warshall
 *
 * @param {number[][]} times - [u, v, w]: directed edge u→v with delay w
 * @param {number} n         - number of nodes (1-indexed)
 * @param {number} k         - source node
 * @return {number}          - time for signal to reach all nodes, or -1
 */
var networkDelayTime = function(times, n, k) {
  const INF = Infinity;

  // Convert 1-indexed to 0-indexed
  const dist = Array.from({ length: n }, (_, i) =>
    Array.from({ length: n }, (_, j) => (i === j ? 0 : INF))
  );

  for (const [u, v, w] of times) {
    dist[u - 1][v - 1] = Math.min(dist[u - 1][v - 1], w);
  }

  // Floyd-Warshall
  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] !== INF && dist[mid][j] !== INF) {
          dist[i][j] = Math.min(dist[i][j], dist[i][mid] + dist[mid][j]);
        }
      }
    }
  }

  // Answer for source k (0-indexed: k-1)
  const src = k - 1;
  let maxDelay = 0;
  for (let j = 0; j < n; j++) {
    if (dist[src][j] === INF) return -1; // node j unreachable
    maxDelay = Math.max(maxDelay, dist[src][j]);
  }
  return maxDelay;

  // Bonus: now dist[i] holds delays from ANY source i — O(1) queries!
};

console.log(networkDelayTime([[2,1,1],[2,3,1],[3,4,1]], 4, 2)); // 2
console.log(networkDelayTime([[1,2,1]], 2, 1));                  // 1
console.log(networkDelayTime([[1,2,1]], 2, 2));                  // -1 (node 1 unreachable from 2)
Problem 3: Transitive Closure of a Graph

Given a directed graph, determine for each pair (i, j) whether j is reachable from i. This is useful for dependency resolution, privilege escalation checks, and reachability queries.

JS
/**
 * Transitive Closure — return a boolean matrix
 * reach[i][j] = true means vertex j is reachable from vertex i
 */
function buildTransitiveClosure(n, edges) {
  const reach = Array.from({ length: n }, (_, i) =>
    Array.from({ length: n }, (_, j) => i === j)
  );

  for (const [u, v] of edges) {
    reach[u][v] = true;
  }

  for (let k = 0; k < n; k++) {
    for (let i = 0; i < n; i++) {
      for (let j = 0; j < n; j++) {
        if (reach[i][k] && reach[k][j]) {
          reach[i][j] = true;
        }
      }
    }
  }

  return reach;
}

// Example: 4 nodes, edges form 0→1→2→3 (chain)
const edges = [[0,1],[1,2],[2,3]];
const reach = buildTransitiveClosure(4, edges);

console.log("Reachability matrix:");
reach.forEach((row, i) =>
  console.log(`  ${i}:`, row.map(b => b ? "T" : "F").join(" "))
);
// 0: T T T T   (0 can reach everything)
// 1: F T T T   (1 can reach 1,2,3)
// 2: F F T T   (2 can reach 2,3)
// 3: F F F T   (3 can only reach itself)
Problem 4: Detecting a Negative Cycle

After running Floyd-Warshall, inspect the main diagonal: if dist[i][i] < 0 for any i, a negative cycle exists in the graph. This is useful for detecting arbitrage opportunities in currency exchange or impossibility in certain scheduling problems.

JS
/**
 * Return all vertices that lie on a negative cycle.
 */
function findNegativeCycleVertices(n, edges) {
  const INF = Infinity;
  const dist = Array.from({ length: n }, (_, i) =>
    Array.from({ length: n }, (_, j) => (i === j ? 0 : INF))
  );

  for (const [u, v, w] of edges) {
    dist[u][v] = Math.min(dist[u][v], w);
  }

  for (let k = 0; k < n; k++) {
    for (let i = 0; i < n; i++) {
      for (let j = 0; j < n; j++) {
        if (dist[i][k] !== INF && dist[k][j] !== INF) {
          dist[i][j] = Math.min(dist[i][j], dist[i][k] + dist[k][j]);
        }
      }
    }
  }

  // Vertices with dist[v][v] < 0 are on a negative cycle
  const onNegCycle = [];
  for (let v = 0; v < n; v++) {
    if (dist[v][v] < 0) onNegCycle.push(v);
  }
  return onNegCycle;
}

// Graph: 0→1 (2), 1→2 (-5), 2→0 (1)
// Cycle 0→1→2→0 has weight 2 + (-5) + 1 = -2 (negative!)
const edgesNeg = [[0,1,2],[1,2,-5],[2,0,1]];
console.log("Vertices on negative cycle:", findNegativeCycleVertices(3, edgesNeg));
// [0, 1, 2] — all three are part of the cycle

// Graph with no negative cycle
const edgesPos = [[0,1,3],[1,2,1],[2,0,5]];
console.log("Vertices on negative cycle:", findNegativeCycleVertices(3, edgesPos));
// [] — cycle weight = 3+1+5 = 9 (positive)
Common Pitfalls
  • Integer overflow: when using large finite values instead of Infinity, dist[i][k] + dist[k][j] can overflow — use Infinity or check both values before adding

  • Forgetting 0-indexing: many problems use 1-indexed vertices; subtract 1 when building the matrix

  • Undirected graphs: always set both dist[u][v] and dist[v][u] during initialization

  • Parallel edges: when multiple edges exist between the same pair, keep only the minimum weight

  • Trusting results when negative cycles exist: dist values are meaningless for vertices on or reachable through a negative cycle — always check dist[i][i] first

Practice Problems
  1. LeetCode 1334 — Find the City With the Smallest Number of Neighbors at a Threshold Distance

  2. LeetCode 743 — Network Delay Time

  3. LeetCode 399 — Evaluate Division (Floyd-Warshall on a ratio graph)

  4. LeetCode 787 — Cheapest Flights Within K Stops (variant — Bellman-Ford fits better, but Floyd-Warshall works for all-pairs version)

  5. CSES — Shortest Routes II (classic all-pairs Floyd-Warshall)

  6. CSES — High Score (longest path with negative cycle detection)

  7. GeeksForGeeks — Transitive Closure of a Graph

  8. LeetCode 1462 — Course Schedule IV (transitive closure variant)

Note
Floyd-Warshall is one of the most elegant dynamic programming algorithms. Its triple loop is deceptively simple — each iteration k adds vertex k to the set of allowed intermediates, progressively relaxing all shortest paths until the globally optimal distances emerge.