Cairn
algorithms · network flow · O(E · max flow) for integer capacities — bounded by the flow value, not the graph's own size

back to Network Flow

Maximum Flow (Ford-Fulkerson)

Ford-Fulkerson is the general method underneath every one of this site's max-flow entries: given a flow network (a directed graph where every edge has a capacity, plus a source and a sink), repeatedly find any path from source to sink with spare capacity left along its entire length, push as much flow as the tightest edge on that path allows — its bottleneck — and repeat until no such path remains. Alongside the forward capacity, every unit of flow pushed also opens a same-sized reverse residual edge, representing "this unit could be undone" — without it, an early path choice can permanently lock out a better combination (see Pitfalls). Edmonds-Karp is this exact method with one added rule: always take the augmenting path with the fewest edges, found by breadth-first search. This page is about the method with that rule removed — what stays true when the path is chosen by any means at all, and what breaks.

Try it

Four nodes, source S to sink T, five directed edges each labeled flow/capacity. S→A, S→B, A→T, and B→T all carry capacity 4; one thin bridge, A→B, carries capacity 1. Pick a path-choice rule below, then press Step or Run to repeatedly find an augmenting path under that rule, push its bottleneck, and repeat. A black-bordered node and thick black edge mark the path just found; orange marks any edge currently carrying flow. When no path remains, the demo reveals the matching minimum cut, same as Edmonds-Karp's own page.

augmenting paths: 0 · max flow so far: 0
Press Step or Run.

Switch the rule and press Reset: the bridge-preferring rule always chooses whichever available path crosses the thin A→B edge (forward or, once that's saturated, its reverse residual edge) over a direct S→A→T or S→B→T path, and takes 8 augmentations — one unit of flow at a time — to reach the max flow of 8. Shortest-path-first, Edmonds-Karp's own rule, never touches the bridge at all: it finds the two length-2 paths S→A→T and S→B→T directly and reaches the identical 8 in 2 augmentations. Same graph, same final answer, four times the work — purely from which path got chosen first.

Any rule is correct — but not equally fast

The augmenting path theorem is what makes Ford-Fulkerson correct regardless of path choice: a flow is maximum if and only if its residual graph has no remaining path from source to sink. So "keep finding augmenting paths until none are left" always terminates at the true maximum, no matter which path gets picked at each step — there's nothing in the method that prefers a short path over a long one, or a "sensible-looking" one over an awkward one. For integer capacities, it also always terminates: every augmentation raises the total flow by at least 1, and the total is capped above by the sum of the source's outgoing capacities, so the number of augmentations can never exceed that sum. That's a real bound — just not one that depends on the graph's size the way Edmonds-Karp's BFS rule does. A network whose capacities are large relative to its node and edge count can force far more augmentations than a graph-size-only bound would ever predict, exactly what the demo above measures directly on a network small enough to step through by hand.

Edmonds-Karp's fix isn't a different method — it's the same method with the path-choice step pinned down: always shortest, by number of edges, found by plain breadth-first search of the residual graph. That single constraint is what turns "eventually finishes" into "finishes within O(VE) augmentations, regardless of what the capacities are" (see that page's own Complexity section for the argument). Every other augmenting-path entry on this site — Dinic's algorithm, Minimum-Cost Maximum Flow — also picks a specific rule for the same reason: "any path" is enough for correctness, never enough for a useful speed guarantee.

Reference implementation

The bare method, with an unspecified findPath — plug in breadth-first search and this becomes Edmonds-Karp; plug in depth-first search, or the demo's own bridge-preferring rule, and it's still correct, just not equally fast:

function fordFulkerson(numNodes, edges, s, t, findPath) {
  // edges: [{ a, b, cap }, ...] — directed, a → b
  // findPath(cap, flow, s, t) returns an array of node indices from s to t
  // with positive residual capacity on every edge, or null if none exists.
  const cap = Array.from({ length: numNodes }, () => new Array(numNodes).fill(0));
  edges.forEach(e => { cap[e.a][e.b] += e.cap; });
  const flow = Array.from({ length: numNodes }, () => new Array(numNodes).fill(0));
  let total = 0;

  while (true) {
    const path = findPath(cap, flow, s, t);
    if (!path) break; // no augmenting path left — done, this is the max flow

    let bottleneck = Infinity;
    for (let i = 0; i < path.length - 1; i++) {
      bottleneck = Math.min(bottleneck, cap[path[i]][path[i + 1]] - flow[path[i]][path[i + 1]]);
    }
    for (let i = 0; i < path.length - 1; i++) {
      const u = path[i], v = path[i + 1];
      flow[u][v] += bottleneck;
      flow[v][u] -= bottleneck; // opens the reverse residual edge
    }
    total += bottleneck;
  }

  return total;
}

Pitfalls

The path-choice rule is not an implementation detail — it changes how many augmentations the method needs by a factor that grows with the capacities themselves. Verified directly on the demo's own four-node network above: with capacities 4 on the four outer edges and 1 on the thin A→B bridge, a rule that always prefers a path using the bridge takes exactly 8 augmentations (one unit of flow each) to reach the max flow of 8, alternating S→A→B→T and S→B→A→T (the second using the bridge's reverse residual edge) until both outer S-side edges are saturated. Edmonds-Karp's breadth-first rule reaches the identical 8 in 2 augmentations, because a length-2 path never needs the longer bridge route at all. Scale every outer capacity from 4 to C and the bridge-preferring rule's augmentation count scales to 2C right along with it — worse and worse relative to Edmonds-Karp's fixed 2, with the graph's own size never changing.

Reverse residual edges aren't an optimization — they're required for correctness, and this page's demo relies on them the same way Edmonds-Karp's does: the bridge-preferring rule's second augmenting path, S→B→A→T, only exists because pushing flow on A→B earlier opened a B→A reverse edge to undo it. Skip that reverse edge and a rule that happens to send the first unit of flow through the bridge gets permanently stuck below the true maximum — see Edmonds-Karp's own Pitfalls for a minimal graph where this is checked directly (final flow 1 instead of the true 2).

Real-valued capacities can break the termination guarantee entirely, not just the speed. The "every augmentation adds at least 1" argument above assumes integer capacities. With irrational capacities and an adversarial path-choice rule, Ford-Fulkerson can be constructed to augment forever, converging toward a total that's strictly less than the true max flow and never reaching it — a real, documented property of the naive method, not a hypothetical. Edmonds-Karp's breadth-first rule sidesteps this completely: its O(VE) bound on the number of augmentations comes from the graph's own size (path lengths and edge count), never from the capacity values, so it terminates in a bounded number of steps no matter what the capacities are. This page's demo only ever uses small integers, so this particular failure mode isn't something it can show live.

Complexity

Time: O(E · F) with an adjacency-list residual graph, where F is the value of the max flow — each path search costs O(E), and the number of augmentations is bounded by F for integer capacities, since every augmentation raises the total by at least 1. This demo represents the graph as a dense V×V capacity matrix rather than adjacency lists (same choice Edmonds-Karp's own demo makes), so each search costs O(V²) here, for a total of O(V² · F). The key difference from Edmonds-Karp's O(VE²) (or O(V³) on this same dense representation): that bound depends only on the graph's own size, this one depends on F — and F can be made arbitrarily large relative to V and E by simply raising the capacities, which is exactly what the demo's capacity slider-free "scale C" argument above shows. Space: O(V²) for the capacity and flow matrices as implemented here, or O(V + E) with adjacency lists.

See Choosing a Network Flow Algorithm for how all eleven of the site's Network Flow entries compare side by side, and why a specific path-choice rule — Edmonds-Karp's breadth-first search, or Dinic's batched version of the same idea — is what turns this page's general method into something worth actually running on a large graph.