Mid-level 11 min · March 24, 2026

Maximum Flow — Edmonds-Karp Prevents Billion Iterations

A 10^9-iteration DFS Ford-Fulkerson killed a pipeline for 24 hours.

N
Naren · Founder
Plain-English first. Then code. Then the interview question.
About
 ● Production Incident 🔎 Debug Guide
Quick Answer
  • Maximum flow problem: route max flow from source to sink in a directed graph with edge capacities, subject to capacity and conservation constraints.
  • Ford-Fulkerson is a method: augmenting path loop; path-finding strategy determines complexity.
  • Edmonds-Karp = Ford-Fulkerson + BFS; O(VE²) guarantee by always picking shortest augmenting paths.
  • Residual graph with backward edges enables flow cancellation; missing backward edges is the #1 bug.
  • Max-flow min-cut theorem: max flow equals minimum cut capacity; one algorithm gives two answers.
Plain-English First

Imagine a city water network: a reservoir (source) feeds homes (sink) through pipes, each with a maximum capacity. The maximum flow problem asks — how much water can flow simultaneously from source to sink? Ford-Fulkerson answers this by repeatedly finding any route and pushing water through it until no route remains. Edmonds-Karp improves this by always choosing the shortest route first (BFS), which prevents worst-case blow-ups and guarantees the algorithm finishes in polynomial time regardless of capacity values.

Maximum flow is one of the most underrated algorithmic primitives in combinatorial optimisation. Bipartite matching, project selection with dependencies, image segmentation, airline crew scheduling, bandwidth allocation — all reduce cleanly to max flow. When you see a problem involving 'maximum throughput through a constrained network', max flow is almost always the right abstraction.

Ford-Fulkerson (1956) established the augmenting-path framework. Edmonds-Karp (1972) fixed its fatal flaw: always pick the shortest augmenting path using BFS. That single constraint moved worst-case complexity from 'possibly exponential' to 'provably O(VE²)' — and that guarantee is what separates a toy implementation from something you can actually deploy.

What is the Maximum Flow Problem?

The **maximum flow problem** asks: given a directed graph G = (V, E) where every edge (u, v) has a non-negative integer capacity c(u, v), and two designated nodes — a source s and a sink t — what is the maximum total flow that can be routed from s to t simultaneously?

Formal constraints on any valid flow assignment f
  • Capacity constraint: f(u, v) ≤ c(u, v) for every edge — you cannot push more through a pipe than it can carry
  • Conservation constraint: for every intermediate node v (not s or t), total inflow = total outflow — nothing is created or destroyed mid-network

The value of the flow is the total amount leaving s (or equivalently, arriving at t).

Why greedy path selection fails. The naive approach — find a path, push as much flow as possible, repeat — is wrong. Paths share edges. A locally optimal choice on the first path can permanently block a globally better combination of paths. This is exactly why the residual graph and backward edges exist.

The hidden power: max flow solves min cut for free. By the max-flow min-cut theorem, the maximum flow value equals the capacity of the minimum cut — the minimum total capacity of edges you'd need to remove to disconnect s from t entirely. One algorithm, two answers. That duality is what makes max flow worth learning deeply.

Production Insight
In production max-flow reductions, the bottleneck is not the algorithm but the graph construction. Wrong capacity assignments (e.g., using int where overflow occurs) silently produce incorrect flows. Always test with known min-cut values.
The max-flow min-cut duality is not just a theorem — it is a debugging tool. If your flow value doesn't match an independently computed min cut, your implementation has a bug.
Key Takeaway
Maximum flow = min cut.
Greedy path selection fails because shared edges create dependencies.
Residual graph with backward edges is the mechanism that finds the global optimum.

What is the Ford-Fulkerson Method?

Ford-Fulkerson (Lester Ford and Delbert Fulkerson, 1956) is not a single algorithm — it is a method or framework. It defines what to do but deliberately leaves open how to find the augmenting path.

The Ford-Fulkerson loop: 1. Find any path from s to t in the residual graph with available capacity 2. Push flow equal to the bottleneck (minimum residual capacity on that path) 3. Update the residual graph (forward edges decrease, backward edges increase) 4. Repeat until no path from s to t exists

The framework is correct. The complexity depends entirely on step 1.

With DFS path selection on a graph with integer capacities, Ford-Fulkerson terminates — but can require O(f) augmentations where f is the total max flow value. If f = 10⁹, that is a billion iterations. On adversarial graphs with large capacities, DFS Ford-Fulkerson is practically useless.

Ford-Fulkerson is best thought of as the theoretical foundation. In practice, you always instantiate it with a specific path-finding strategy. The most important instantiation is Edmonds-Karp.

Ford-Fulkerson with DFS is not safe in production
If someone writes 'Ford-Fulkerson' in a codebase and uses DFS, the time complexity is O(E · f) — potentially exponential for large capacity values. Always use BFS (Edmonds-Karp) or Dinic's algorithm. Ford-Fulkerson with DFS belongs in textbook examples only.
Production Insight
The Ford-Fulkerson method is taught without qualification, leading many engineers to implement DFS by default. In production, this is a ticking time bomb. Worst-case inputs are rare but devastating — a 6-node adversarial graph with capacity 10⁶ causes 2×10⁶ iterations.
Common wisdom: if your augmenting path uses a stack, you're not ready for production.
Key Takeaway
Ford-Fulkerson is a framework, not an algorithm.
The path-finding strategy determines complexity.
DFS can be exponential — never use it in production.

What is the Edmonds-Karp Algorithm?

Edmonds-Karp (Jack Edmonds and Richard Karp, 1972) is Ford-Fulkerson with one specific, decisive constraint: always find the shortest augmenting path using BFS.

That single constraint — fewest hops, found by BFS — changes the complexity from O(Ef) to O(VE²): polynomial time, always, regardless of capacity values.

Why does it work? BFS guarantees that the distance from s to any node in the residual graph is non-decreasing across iterations. This bounds the total number of augmentations to O(VE), and since each BFS is O(E), the total is O(VE²).

In one sentence: Edmonds-Karp is the correct, production-safe implementation of the maximum flow algorithm for most use cases.

When to use Edmonds-Karp vs alternatives: - Edmonds-Karp O(VE²) — default choice; clean implementation, polynomial guaranteed, fine for V ≤ 500 - Dinic's O(V²E) — faster in practice on dense graphs; use when profiling shows Edmonds-Karp is a bottleneck - Push-relabel O(V²√E) — best theoretical complexity for general graphs; complex to implement correctly - Ford-Fulkerson with DFS — never in production; textbook examples only

For 99% of interview questions and most real systems, Edmonds-Karp is the right answer.

Production Insight
Edmonds-Karp's O(VE²) bound is tight for some graphs (e.g., dense networks with unit capacities). For V=1000, E=500k, this is ~2.5×10¹¹ operations — too slow. In such cases, profile before assuming Edmonds-Karp is enough. Dinic's algorithm often performs 10-100x faster on dense graphs.
The BFS guarantee is the only thing protecting you from exponential blow-up. Never replace BFS with DFS.
Key Takeaway
Edmonds-Karp = Ford-Fulkerson + BFS.
O(VE²) polynomial guarantee always.
Default choice for V ≤ 500; profile for larger.

Residual Graphs — The Core Data Structure

Every max flow algorithm lives or dies on its residual graph implementation. Get this wrong and the algorithm silently produces incorrect results.

After pushing flow through an edge, you track two quantities
  • Forward residual (u → v): how much more flow can go this way = capacity − current_flow
  • Backward residual (v → u): how much flow can be cancelled = current_flow

The residual graph contains both of these for every original edge. It is updated after each augmentation — never recomputed from scratch.

Why backward edges exist — the undo mechanism.

An earlier greedy choice may have used an edge that blocks a better global solution. The backward edge lets the algorithm redirect previously-committed flow: pushing flow along (v, u) in the residual graph effectively cancels f units of flow from the earlier (u, v) assignment. Without backward edges, you cannot recover from suboptimal early choices and the algorithm gets permanently stuck below the true maximum.

The initialisation rule is fixed and non-negotiable: every backward edge starts at 0. The update rule is equally fixed: when you push f units along (u, v), do residual[u][v] -= f and residual[v][u] += f. Both updates happen atomically — one without the other is a bug.

The single most common max-flow bug
Forgetting to initialise backward edges to 0, or updating only the forward residual without updating the backward residual. Symptom: algorithm terminates early with a flow value that is correct-looking but 20–60% below optimal. The fix is always the same: ensure every (u,v) edge in the original graph has a corresponding (v,u) entry initialised to 0 in the residual graph, and that both are updated on every augmentation.
Production Insight
The most common bug in max flow implementations is forgetting to initialise backward edges to 0. In production codebases, this manifests as mysterious underflows that are hard to reproduce because they depend on augmenting path order.
Always write unit tests that verify the residual graph invariants: for any edge (u,v), residual[u][v] + residual[v][u] should equal the original capacity after any sequence of augmentations.
Key Takeaway
Backward edges are mandatory — they enable flow cancellation.
Forward residual = remaining capacity.
Backward residual = cancellable flow.
Atomic update on every augmentation: both forward and backward.

Edmonds-Karp: Step by Step

Step 1 — Build the residual graph Copy the original graph adjacency structure. For every directed edge (u, v) with capacity c, add the reverse edge (v, u) with residual capacity 0 if it does not already exist. This is a one-time setup before the main loop.

Step 2 — BFS from s to t Run BFS on the current residual graph. Only traverse edges with residual capacity > 0. Record the parent of each visited node. If t is never reached, the algorithm terminates — no augmenting path exists and the current flow is maximum.

Step 3 — Find the bottleneck Reconstruct the path from t back to s using the parent map. The bottleneck is min(residual[u][v]) across all edges on the path. This is the maximum flow increment for this iteration.

Step 4 — Augment** For every edge (u, v) on the path
  • residual[u][v] -= bottleneck
  • residual[v][u] += bottleneck

This is the atomic update. Both lines are required. The backward edge update is what enables future rerouting.

Step 5 — Accumulate and repeat max_flow += bottleneck. Go to Step 2.

The critical insight about BFS vs DFS: BFS produces the shortest path (fewest edges). Edmonds and Karp proved that with shortest-path augmentation, the distance from s to any node in the residual graph is non-decreasing across iterations. This limits the total augmentations to O(VE) — making the whole algorithm O(VE²) regardless of capacity values. DFS has no such guarantee.

Production Insight
Step 4 (augment) is where 90% of bugs hide. A common mistake is to update only the forward edge and forget the backward edge. In a large graph, this can go undetected for many iterations, producing a wrong flow that looks 'close enough'.
Use a helper function that asserts residual invariants after each augmentation during debugging.
Key Takeaway
Five steps: build residual graph, BFS for path, find bottleneck, augment atomically, accumulate.
BFS vs DFS: BFS guarantees non-decreasing distances → bounded iterations.
Never skip backward edge update.

Proof of Non-decreasing Distances in Edmonds-Karp

Why does BFS augmentation bound the number of iterations to O(VE)? The core lemma is: during Edmonds-Karp, for every vertex v, the shortest-path distance d(v) from s in the residual graph is non-decreasing across augmentations.

Proof outline, by contradiction: Let d_f(v) be the distance just before an augmentation along path P (bottleneck edge e). Suppose after augmentation, the distance to some vertex x strictly decreases: d_f'(x) < d_f(x). Consider the moment when d_f'(x) first drops. Since the only changes to the residual graph are along P, the new shorter path must use a backward edge of P (because forward edges only lose capacity). Let (u, v) be the first backward edge on the new path, so that d_f'(u) = d_f'(v) - 1 (distance via BFS ordering). Because (u, v) is a backward edge, the original edge (v, u) was in P. Before augmentation, d_f(u) ≤ d_f(v) - 1 by the BFS shortest-path property? Wait, careful: The original BFS distances before augmentation satisfy d_f(v) = d_f(u) + 1 because P is a shortest path. After augmentation, the residual graph now has a backward edge (u, v) with positive capacity. In the new shorter path, the distance to u is at least d_f'(u). Contradiction arises by using the triangle inequality and assumed decrease.

The formal lemma: If a vertex v has its distance decrease, then there exists some edge (u, v) used in the new path that was a backward edge of the augmenting path. By construction, d_f(u) = d_f(v) - 1 from the previous BFS (since (v, u) was on the shortest path). But after augmentation, d_f'(v) = d_f'(u) + 1. Combining leads to d_f'(v) ≥ d_f(v), contradiction. Therefore distances can never decrease.

Implication: Each vertex can experience at most V distance increases (since distance max is V). Each augmenting path contains at least one edge whose distance to s is guaranteed to increase after a certain number of times? Actually, each edge (u, v) can become a bottleneck at most O(V) times because each time the distance to either u or v increases. So augmentations ≤ O(VE).

This proof is what separates an exponential heuristic from a polynomial algorithm. Edmonds and Karp were the first to formalise it, and it remains the foundational argument for bounded augmentation counts in BFS-based augmenting path algorithms.

Production Insight
In a production code review, the 'non-decreasing distance' lemma is rarely cited, but its consequence — polynomial runtime — is taken for granted. If you ever replace BFS with a heuristic like 'use most promising edge first', you lose this guarantee. Any deviation from pure BFS path selection must be justified with explicit worst-case analysis.
The lemma also implies that for larger V (e.g., 10k), the O(VE²) bound may still be too heavy — not because the lemma is false, but because V² becomes large. This is the point where Dinic's level graph or push-relabel becomes necessary.
Key Takeaway
The distance to any node never decreases during Edmonds-Karp.
This monotonicity bounds augmentations to O(VE), leading to O(VE²) overall.
Proof uses contradiction: a decreasing distance would require a backward edge from a previous shortest path.

Worked Example — Tracing Every Iteration

Graph: `` s → a (cap 10) s → b (cap 10) a → t (cap 10) a → b (cap 2) b → t (cap 10) ` Initial residual graph (all backward edges start at 0): ` s→a:10 a→s:0 s→b:10 b→s:0 a→t:10 t→a:0 a→b: 2 b→a:0 b→t:10 t→b:0 ``

Iteration 1: - BFS queue: [s] → dequeue s, enqueue a (res=10), enqueue b (res=10) - Dequeue a, enqueue t (res=10). t reached — stop BFS. - Shortest path: s → a → t (2 hops) - Bottleneck: min(10, 10) = 10 - Update: s→a: 10→0, a→s: 0→10; a→t: 10→0, t→a: 0→10 - max_flow = 10

Iteration 2: - BFS queue: [s] → dequeue s; s→a has residual 0 (skip); enqueue b (res=10) - Dequeue b, enqueue t (res=10). t reached — stop BFS. - Shortest path: s → b → t (2 hops) - Bottleneck: min(10, 10) = 10 - Update: s→b: 10→0, b→s: 0→10; b→t: 10→0, t→b: 0→10 - max_flow = 20

Iteration 3: - BFS queue: [s] → dequeue s; s→a residual=0 (skip), s→b residual=0 (skip) - Queue empties. t never reached. - Terminate. Maximum flow = 20.

Observation: edge a→b (cap 2) was never used. BFS found two direct 2-hop paths and saturated both. The a→b edge would only appear in a solution if one of those direct paths were blocked — and here, both were available from the start. This is exactly the kind of clean, predictable behaviour that makes Edmonds-Karp the default choice.

Production Insight
Worked examples are essential for testing implementations. The example here is deliberately simple to illustrate the algorithm, but real graphs have tens of thousands of edges. When debugging, start with a known small example like this to verify core logic before scaling.
Always include a test case where the a→b edge must be used to achieve max flow (the classic adversarial graph where DFS fails).
Key Takeaway
Trace through iterations to build intuition.
The algorithm terminates when BFS cannot reach t.
The a→b edge may be unused if direct paths suffice.

Python Implementation

The code below maps directly to the five steps above. Every comment references the step it implements.

edmonds_karp.pyPYTHON
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
from collections import deque

def edmonds_karp(graph: dict, source: str, sink: str) -> int:
    """
    Edmonds-Karp: Ford-Fulkerson with BFS augmenting paths.
    graph: {node: {neighbour: capacity}}
    Returns maximum flow from source to sink. O(VE^2).
    """
    # Step 1: build residual graph
    # Copy original capacities; add backward edges initialised to 0
    residual = {u: dict(v_cap) for u, v_cap in graph.items()}
    for u in graph:
        for v in graph[u]:
            if v not in residual:
                residual[v] = {}
            if u not in residual[v]:
                residual[v][u] = 0  # backward edge — must exist

    def bfs_augmenting_path():
        """Steps 2–3: BFS for shortest path, return path + bottleneck."""
        parent = {source: None}
        queue = deque([source])

        while queue:
            node = queue.popleft()
            if node == sink:
                break
            for neighbour, cap in residual[node].items():
                if neighbour not in parent and cap > 0:
                    parent[neighbour] = node
                    queue.append(neighbour)

        if sink not in parent:
            return None, 0  # no augmenting path — algorithm terminates

        # Reconstruct path and find bottleneck
        path, node = [], sink
        while node != source:
            prev = parent[node]
            path.append((prev, node))
            node = prev

        bottleneck = min(residual[u][v] for u, v in path)
        return path, bottleneck

    max_flow = 0
    while True:
        path, bottleneck = bfs_augmenting_path()
        if path is None:
            break

        # Step 4: augment — both updates are mandatory
        max_flow += bottleneck
        for u, v in path:
            residual[u][v] -= bottleneck  # forward: capacity consumed
            residual[v][u] += bottleneck  # backward: undo capacity created

    return max_flow  # Step 5 complete


graph = {
    's': {'a': 10, 'b': 10},
    'a': {'t': 10, 'b': 2},
    'b': {'t': 10, 'a': 0},
    't': {}
}
print(edmonds_karp(graph, 's', 't'))  # 20
Output
20
Production Insight
The provided implementation is correct but not optimised for production. In Python, using dicts for the residual graph is fine for V≤500. For larger graphs, use a 2D list or numpy array indexed by precomputed node IDs. Also, the repeated BFS creation of parent dicts and path list can be optimised with preallocated arrays.
In production, never recompute the residual graph from scratch each time — the code updates it in place correctly.
Key Takeaway
Implementation maps directly to the 5 steps.
Use dict for small graphs; 2D array for larger.
Always include edge case: source equals sink returns 0.

The Max-Flow Min-Cut Theorem

The maximum flow from s to t equals the capacity of the minimum cut.

A cut partitions all nodes into two sets: S (containing s) and T (containing t). The cut's capacity is the sum of capacities of all edges crossing from S to T (not T to S — direction matters).

The minimum cut is the partition that minimises this total crossing capacity. It represents the true bottleneck of the network: the set of edges whose removal would disconnect s from t at lowest cost.

Reading the minimum cut from Edmonds-Karp's output: After the algorithm terminates, run BFS on the final residual graph from s. Every node reachable from s belongs to set S; every unreachable node belongs to set T. The edges from S to T in the original graph are all saturated (residual capacity = 0). These are your minimum cut edges.

Why this duality is powerful: - Network reliability: the minimum cut identifies the least-resilient links — removing them disconnects the network at minimum cost - Bipartite matching: by König's theorem, maximum bipartite matching = max flow on a unit-capacity graph; min cut = minimum vertex cover - Project selection: model projects as nodes with profit/dependency edges; max flow gives the optimal project set - Image segmentation: pixels are nodes; intensity differences are capacities; min cut separates foreground from background

This single theorem is why max flow shows up in computer vision, operations research, computational biology, and network engineering. It is not just an algorithm — it is a modelling tool.

Applications of Max-Flow Min-Cut
Bipartite matching (König's theorem), airline crew scheduling, image segmentation (foreground/background as s/t cut), project selection with profit/dependency constraints, baseball elimination, network reliability analysis — all reduce to max flow. Recognising these reductions is the skill that separates someone who knows the algorithm from someone who can apply it.
Production Insight
In system design interviews, the max-flow min-cut theorem is frequently used to argue about reliability: the minimum cut identifies the links whose failure would disconnect the network at least cost. But in practice, cuts are more complex because edges have failure probabilities. Use min cut as a worst-case bound, not an exact reliability metric.
The real power of min cut is in bipartite matching: min cut equals minimum vertex cover, which translates to minimum number of resources to cover all tasks.
Key Takeaway
Max flow = min cut capacity.
Extract min cut: BFS from s on final residual graph.
Saturated edges from reachable to unreachable = cut edges.
Applications: reliability, matching, segmentation.

Complexity — When to Use Which Algorithm

AlgorithmTime ComplexityNotes
Ford-Fulkerson (DFS)O(E · f)f = max flow value; exponential for large integer capacities
Edmonds-Karp (BFS)O(VE²)Polynomial always; the default correct choice
Dinic's algorithmO(V²E)Faster in practice; use when Edmonds-Karp is too slow
Push-relabelO(V²√E)Best asymptotic; complex to implement; rarely needed

Why Edmonds-Karp is O(VE²): BFS guarantees shortest paths. A key lemma proves that under BFS augmentation, the distance d(s, v) in the residual graph is non-decreasing for every node v. Since distances are bounded by V, and each 'distance increase' event can happen at most O(V) times per edge, the total augmentations are bounded by O(VE). Each BFS costs O(E). Total: O(VE²).

Decision rule for production: 1. Default to Edmonds-Karp — clean implementation, O(VE²) guarantee, fine for V ≤ 500 2. Profile before switching — Dinic's is faster in practice but more complex to implement correctly 3. Never use Ford-Fulkerson with DFS in production code — the exponential risk is not worth it

Interview answer: Edmonds-Karp is what interviewers expect. State the O(VE²) complexity, explain why BFS prevents the exponential blow-up, and mention that Dinic's exists for cases where you need better practical performance.

Production Insight
The complexity table is a starting point. Real-world performance depends on graph density, capacity distribution, and implementation details. Dinic's often beats Edmonds-Karp by 10x even on moderate graphs because of its level graph optimisation. If you're building a system that calls max flow repeatedly, invest in implementing Dinic's or use a library like NetworkX, but be aware of Python's overhead.
Key Takeaway
Default: Edmonds-Karp O(VE²).
Profile before switching to Dinic's.
Never use DFS Ford-Fulkerson.
Interview expected answer: Edmonds-Karp with BFS.
Algorithm Selection Decision Tree
IfV ≤ 500 and graph not extremely dense
UseUse Edmonds-Karp — simple to implement, polynomial guarantee.
IfDense graph (E close to V²) or V > 500
UseUse Dinic's algorithm — O(V²E) often faster in practice.
IfGraph with very large capacities (up to 10⁹)
UseUse Edmonds-Karp or Dinic's; both are capacity-independent.
IfNeed to extract min cut for each max flow computation
UseAny algorithm works, but Edmonds-Karp's BFS after termination directly gives the cut.
● Production incidentPOST-MORTEMseverity: high

The Billion-Iteration Disaster: When DFS Ford-Fulkerson Killed a Pipeline

Symptom
Job ran for over 24 hours without completing; CPU at 100%, no errors.
Assumption
The graph is small, so any algorithm should finish quickly.
Root cause
DFS Ford-Fulkerson on an adversarial graph with capacity 10⁶ caused O(E·f) = 10⁹ iterations.
Fix
Switch to BFS (Edmonds-Karp) — same bottleneck computation, but completed in <1 second.
Key lesson
  • Never use DFS for augmenting path selection in production.
  • Always benchmark with worst-case capacity values.
  • If your max-flow implementation uses a stack for path finding, replace it with a queue immediately.
Production debug guideSymptom–Action Guide for Common Max Flow Failures4 entries
Symptom · 01
Algorithm returns a flow value lower than expected (20–60% below optimal)
Fix
Check that backward residual edges are initialised to 0 and updated atomically in every augmentation. Add assertion to verify that residual[u][v] + residual[v][u] equals original capacity after each iteration.
Symptom · 02
Algorithm runs extremely slowly on graphs with large capacities
Fix
Verify you are using BFS (queue) for augmenting path, not DFS (stack). Profile number of augmentations; if it exceeds V*E, BFS is not being used.
Symptom · 03
Algorithm produces correct flow but takes too long on dense graphs
Fix
Consider upgrading to Dinic's algorithm for O(V²E) performance. Profile with sample data to confirm bottleneck.
Symptom · 04
Min cut extraction after termination gives wrong edges
Fix
Ensure the BFS from source uses only edges with residual capacity > 0. Verify that you are using the final residual graph, not the original graph.
Algorithm Complexity Comparison
AlgorithmTime ComplexityProduction Suitability
Ford-Fulkerson (DFS)O(E · f)Not suitable; exponential risk
Edmonds-Karp (BFS)O(VE²)Default for V ≤ 500
Dinic'sO(V²E)Faster on dense graphs; recommended for large V
Push-relabelO(V²√E)Best asymptotic but complex; rarely needed

Key takeaways

1
Maximum flow problem
route as much flow as possible from source s to sink t in a directed graph with edge capacities, subject to capacity and conservation constraints. Greedy path selection fails because shared edges create dependencies.
2
Ford-Fulkerson is a method (framework), not an algorithm. The augmenting-path loop is correct; the choice of path-finding strategy determines complexity. With DFS the complexity can be exponential
never use DFS in production.
3
Edmonds-Karp = Ford-Fulkerson + BFS. That single constraint
always pick the shortest augmenting path — limits total augmentations to O(VE) and gives O(VE²) overall. This is the default correct implementation.
4
Residual graph is the core data structure. Forward residual = remaining capacity. Backward residual = cancellable flow. Both must be updated atomically on every augmentation. Missing backward edges is the single most common max-flow bug.
5
Max-flow min-cut theorem
max flow = min cut capacity. After termination, the set of saturated edges crossing from the BFS-reachable set (from s) to the unreachable set is the minimum cut. One algorithm, two answers.

Common mistakes to avoid

4 patterns
×

Forgetting to initialise or update backward residual edges

Symptom
Algorithm terminates with a flow value 20–60% below optimal with no error
Fix
Every original edge (u,v) must have a corresponding (v,u) entry initialised to 0 in the residual graph; both residual[u][v] and residual[v][u] must be updated atomically on every augmentation.
×

Using DFS instead of BFS for augmenting path selection

Symptom
Correct on small graphs, catastrophically slow on graphs with large edge capacities
Fix
BFS only — this is the defining constraint that makes it Edmonds-Karp and gives the O(VE²) bound.
×

Treating Ford-Fulkerson and Edmonds-Karp as identical

Symptom
In interview or design, saying 'I use Ford-Fulkerson' without specifying path finding leaves the algorithm implementation ambiguous
Fix
Always specify 'I use Edmonds-Karp (Ford-Fulkerson with BFS).' Ford-Fulkerson is a framework; Edmonds-Karp is a specific instantiation.
×

Not handling sink outgoing or source incoming edges in residual graph

Symptom
The residual graph may be incomplete, causing the algorithm to miss potential augmenting paths that cancel flow through source/sink
Fix
Always build the full residual graph over all edges, including backward edges for edges incident to source and sink.
INTERVIEW PREP · PRACTICE MODE

Interview Questions on This Topic

Q01JUNIOR
What is the maximum flow problem? State the two formal constraints on a ...
Q02SENIOR
What is the difference between Ford-Fulkerson and Edmonds-Karp? Why is t...
Q03SENIOR
What is a residual graph? Why are backward edges necessary — construct a...
Q04SENIOR
State the max-flow min-cut theorem. How do you extract the minimum cut f...
Q05SENIOR
Why does Edmonds-Karp guarantee O(VE²) while Ford-Fulkerson with DFS doe...
Q06SENIOR
How would you model bipartite matching as a maximum flow problem? What c...
Q07SENIOR
In a production system with V=1000, E=50000, would you use Edmonds-Karp ...
Q08SENIOR
What happens to the algorithm if you forget to update the backward resid...
Q01 of 08JUNIOR

What is the maximum flow problem? State the two formal constraints on a valid flow.

ANSWER
The maximum flow problem asks: given a directed graph G(V,E) with non-negative edge capacities c(u,v), a source s and a sink t, what is the maximum total flow that can be routed from s to t? The two constraints on a valid flow f are: (1) Capacity constraint: f(u,v) ≤ c(u,v) for all edges. (2) Conservation constraint: for every intermediate node v, total inflow = total outflow. The flow value is the total outflow from s.
FAQ · 6 QUESTIONS

Frequently Asked Questions

01
What is the maximum flow algorithm?
02
What is the Ford-Fulkerson method?
03
What is the Edmonds-Karp algorithm?
04
What is the difference between Ford-Fulkerson and Edmonds-Karp?
05
Why can Ford-Fulkerson with DFS be exponential?
06
How do I find the minimum cut after running Edmonds-Karp?
🔥

That's Graphs. Mark it forged?

11 min read · try the examples if you haven't

Previous
Bidirectional Search Algorithm
15 / 17 · Graphs
Next
Eulerian Path and Circuit — Hierholzer's Algorithm