Bellman-Ford
Find cheapest paths from one start even when some edges have negative costs, by relaxing every edge again and again, and detect loops that keep getting cheaper.
O(n * e)Space O(n)3 practice problemsStep-through pictureThe idea
Dijkstra's algorithm breaks when some edges have negative costs, such as a refund or a discount. Bellman-Ford handles them. It is slower but much simpler: it goes over the whole list of edges several times, and each time it tries to improve every distance.
Trying to improve a distance with one edge is called relaxing it: if going to from and then along the edge is cheaper than the best known cost of to, update dist[to]. After one full pass over all edges, every cheapest path that uses at most 1 edge is correct. After two passes, every cheapest path with at most 2 edges is correct, and so on.
A cheapest path never needs to repeat a node, so it has at most n - 1 edges, and n - 1 passes are enough. If one more pass still improves something, the graph has a negative cycle: a loop whose total cost is below zero, which you could go around forever to make the cost as low as you like. Bellman-Ford reports that.
An everyday example
Word of mouth about cheap routes
Travelers share tips once a day: "I can get to town B for 4 coins." Each day, everyone hears every tip and updates their own best price. After one day you know every route that takes one hop; after two days, every route of two hops. With n towns, after n - 1 days nothing should change. If prices are still dropping, some loop of trips actually pays you money to ride it.
| In the example | In the code |
|---|---|
| One day of sharing tips | One round over every edge |
| A traveler's best known price | dist[to] |
| Finding a cheaper route through a friend | dist[from] + weight < dist[to] |
| A quiet day where nothing changes | changed stays false, so stop early |
Prices still dropping after n - 1 days | A negative cycle |
From the slow way to bellman-ford
- Dijkstra's algorithm locks in the closest node early, which is only safe when no edge is negative. A negative edge found later, like a refund, could make a locked node cheaper. The obvious fallback is to try every path, but the number of paths grows exponentially, so that's no good beyond tiny graphs.
- Notice that relaxing an edge is always safe:
dist[to] = dist[from] + weightonly ever stores the cost of a real path, and only when it's cheaper. So we can relax edges in any order, as many times as we like, and never make an answer wrong. The only question is how many times is enough. - Count edges instead of costs. After one pass over every edge, every cheapest path that uses one edge is found. After a second pass, every cheapest path with two edges is found, because its last edge gets relaxed after the first part was already right. A cheapest path never repeats a node, so it has at most
n - 1edges, andn - 1passes are enough, in any edge order. - Two cheap extras finish it. If a pass changes nothing, the next pass would see the same numbers, so stop early. And if one more pass after
n - 1still lowers something, some cheapest path would neednedges, which means it repeats a node through a loop with a negative total. Passes over the edge list, an early stop, and a final check: that loop is Bellman-Ford.
How it works
- Set every
disttoInfinityanddist[start] = 0. - Repeat for
roundfrom 1 ton - 1. - In each round, go through every edge
[from, to, weight]. Skip it iffromhasn't been reached yet. - If
dist[from] + weightis less thandist[to], updatedist[to]and note that somethingchanged. - If a whole round changes nothing, every distance is final: stop early.
- Finally, check every edge once more. If any edge can still improve a distance, there is a negative cycle: return
null. Otherwise returndist.
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 bellmanFord(n, edges, start) {const dist = new Array(n).fill(Infinity);dist[start] = 0;for (let round = 1; round < n; round++) {let changed = false;for (const [from, to, weight] of edges) {if (dist[from] === Infinity) continue;if (dist[from] + weight < dist[to]) {dist[to] = dist[from] + weight;changed = true;}}if (!changed) break;}for (const [from, to, weight] of edges) {if (dist[from] !== Infinity && dist[from] + weight < dist[to]) return null;}return dist;}
How fast it is
| Time | O(n * e) |
|---|---|
| Extra memory | O(n) |
Up to n - 1 rounds each check all e edges. Only the dist array is stored, because it reads the edge list directly.
- Best case
O(e)The edges are listed in the order paths use them, so round 1 finds every price and round 2 changes nothing: about
3eedge checks, including the final check. - Average case
O(k · e)kis the number of rounds that change something, at most the most edges on any cheapest path, plus one quiet round. It's often far belownon graphs with short cheapest paths. - Worst case
O(n · e)A long chain listed backwards, where each round fixes just one more node, or a graph with a negative loop, where all
n - 1rounds run and the final check fails.
Common mistakes
- Running only until the first round with no change and skipping the extra negative-cycle check when the rounds ran out.
- Relaxing edges that start from an unreached node. With negative weights,
InfinitystaysInfinity, but other sentinel values like1e9can drift and look reachable. - Using it for a "at most
kedges" question without copyingdisteach round, which lets one round use several edges. - Using Bellman-Ford on large graphs with only positive costs. Dijkstra gives the same answer much faster.
When to use it
Look for these clues in a problem:
- Some edge costs can be negative, such as refunds, credits, or gains.
- The question asks whether a loop can make the total cost fall forever.
- Paths are limited to at most
kedges orkstops. - The graph is small enough that
n * esteps is fine.
Check yourself
Answer each one in your head first, then open it.
Why are n - 1 rounds always enough when there's no negative loop?
Round k fixes every cheapest path with up to k edges, and a cheapest path never repeats a node, so it has at most n - 1 edges.
In the trace, why did it take three changing rounds instead of one?
The edges were listed backwards along the paths, so each round could only extend paths by about one edge. Listed in path order, round 1 alone finds every price.
Why can't it report a negative loop that the start can't reach?
Nodes on that loop keep dist of Infinity, and line 7 skips their edges. The loop never changes any price, so the final check never sees it.
What does the changed flag save?
Once a round changes nothing, every later round would too, so it can stop right away. On graphs with short cheapest paths, that turns n - 1 rounds into a few.
Practice problems
Stuck on one? Tuto is in the playground too, and gives you hints, never the answer.
0 of 3 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 first rule: every dist[v] is either Infinity or the cost of some real walk from the start to v. That's true at the start, and relaxing sets dist[to] to a walk to from plus one more edge. So dist is never lower than the true cheapest cost, as long as no negative loop exists.
The second rule: after round k, dist[v] is no more than the cheapest cost of any path to v with at most k edges. A path with k edges is a path with k - 1 edges to some u, then the edge u → v. After round k - 1, dist[u] is at most the first part's cost, and round k relaxes u → v, so dist[v] is at most the whole cost.
Without a negative loop, a cheapest path has at most n - 1 edges, so after round n - 1 both rules squeeze dist to the exact answer. A round that changes nothing proves the same thing early, because the next round would read the same values. With a negative loop v₁ → v₂ → ... → v₁ reachable from the start, suppose no edge could improve: then dist[vᵢ₊₁] <= dist[vᵢ] + weight for every edge of the loop. Adding these up around the loop cancels every dist and leaves 0 <= total weight, which contradicts a negative total. So some edge still improves, and the final check returns null.
Going deeper
3 more ideas
Why n - 1 rounds are enough
After round k, every node whose cheapest path uses at most k edges has its correct cost: the last edge of that path is relaxed in round k at the latest, after the node before it was already correct. Without negative cycles, a cheapest path never visits a node twice, so it has at most n - 1 edges. Round n - 1 therefore fixes everything.
Cost
Each round checks every edge, and there are at most n - 1 rounds plus one check, so the time is O(n * e). That's much slower than Dijkstra's O((n + e) log n) on big graphs, so use Bellman-Ford only when you need negative edges or an edge-count limit.
Limiting the number of edges
To find the cheapest path that uses at most k edges, run exactly k rounds, and in each round read distances from a copy of dist made at the start of the round. Without the copy, one round can chain several edges together and break the limit.
Work it by hand
Every step in a table, with why
Five nodes from start 0, with the edges listed in an unhelpful order and one negative edge, so prices keep falling for three rounds.
n = 5, edges = [[3,4,2], [1,3,4], [2,1,-6], [0,2,8], [0,1,3], [1,4,6]], start = 0
| Step | round | Edge | dist[from] + weight | dist after | What happens | Why |
|---|---|---|---|---|---|---|
| 1 | 1 | 3→4, 1→3, 2→1 | dist[from] is ∞ | [0, ∞, ∞, ∞, ∞] | Skip all three | Nodes 3, 1, and 2 haven't been reached yet, so line 7 skips their edges. |
| 2 | 1 | 0→2 (8) | 0 + 8 = 8 | [0, ∞, 8, ∞, ∞] | Improve | The start's edges are the first ones that can do anything. |
| 3 | 1 | 0→1 (3) | 0 + 3 = 3 | [0, 3, 8, ∞, ∞] | Improve | Node 1 gets the direct price 3. |
| 4 | 1 | 1→4 (6) | 3 + 6 = 9 | [0, 3, 8, ∞, 9] | Improve, changed = true | Node 1 was just reached in this same round, so its edge already helps. |
| 5 | 2 | 3→4 (2) | dist[3] is ∞ | [0, 3, 8, ∞, 9] | Skip | Node 3 still has no price at the start of round 2. |
| 6 | 2 | 1→3 (4) | 3 + 4 = 7 | [0, 3, 8, 7, 9] | Improve | Node 3 is reached for the first time, through node 1. |
| 7 | 2 | 2→1 (-6) | 8 - 6 = 2 | [0, 2, 8, 7, 9] | Improve | The negative edge makes 0 → 2 → 1 cost 2, cheaper than the direct 3. Dijkstra would already have locked node 1 at 3. |
| 8 | 2 | 0→2, 0→1 | 8 and 3 | [0, 2, 8, 7, 9] | No change | 8 ties dist[2], and 3 is worse than the new 2. |
| 9 | 2 | 1→4 (6) | 2 + 6 = 8 | [0, 2, 8, 7, 8] | Improve | Node 1's cheaper price flows on to node 4. |
| 10 | 3 | 3→4 (2) | 7 + 2 = 9 | [0, 2, 8, 7, 8] | No change | 9 is not less than 8. Node 3's price of 7 is about to drop anyway. |
| 11 | 3 | 1→3 (4) | 2 + 4 = 6 | [0, 2, 8, 6, 8] | Improve | The last change: node 3 is now reached through the cheap node 1. The rest of round 3 changes nothing. |
| 12 | 4 | all 6 edges | nothing is cheaper | [0, 2, 8, 6, 8] | changed is false: break | A quiet round means every price is final. Round 4 is also the last allowed round, since n - 1 = 4. |
| 13 | check | all 6 edges | nothing is cheaper | [0, 2, 8, 6, 8] | No negative loop | The extra pass on lines 15 and 16 finds no edge that still lowers a price. |
It returns [0, 2, 8, 6, 8] after 4 rounds of 6 edge checks plus the final check of 6. With the same edges listed in path order, round 1 finds every price and round 2 is quiet, so only 2 rounds run.
Compared with other ways
When to pick something else
| Approach | Time | Extra memory | Choose it when |
|---|---|---|---|
| Bellman-Ford | O(n · e) | O(n) | Some edges are negative, you need to detect negative loops, or paths are limited to at most k edges. |
| Dijkstra's algorithm | O(e log n) | O(n + e) | No edge is negative. It gives the same answer much faster on big graphs. |
| Floyd-Warshall | O(n³) | O(n²) | You need every pair of nodes, not just one start, and the graph is small. |
Extra memory: O(n): only the dist array. It reads the edge list directly and never builds adjacency lists.
How the steps grow
- Bellman-Ford
O(n · e) - Dijkstra's algorithm
O(e log n) - Floyd-Warshall
O(n³)
| Approach | n = 10 | n = 1,000 | n = 1,000,000 |
|---|---|---|---|
| Bellman-Ford | 100 | 1 million | 1 trillion |
| Dijkstra's algorithm | 33 | 9,966 | 20 million |
| Floyd-Warshall | 1,000 | 1 billion | more than a quadrillion |
Inputs that need care
6 tricky inputs
- A node that can't be reached from the start
- Its
diststaysInfinity, and line 7 skips its outgoing edges in every round. - A negative loop the start can't reach:
[[0,1,1], [2,3,-2], [3,2,1]] - Nodes 2 and 3 stay
Infinityand are skipped, so it returns[0, 1, Infinity, Infinity]without reporting the loop. To find any negative loop, start everydistat 0 instead. - A reachable negative loop:
[[0,1,2], [1,2,3], [2,3,-1], [3,1,-3]] - The loop
1 → 2 → 3 → 1totals -1, so prices fall every round, and the final check returnsnull. - An undirected graph with a negative edge
- An undirected edge is two directed edges, so a negative one makes a loop
a → b → awith a negative total. It returnsnull; negative undirected edges need a different model. - One node,
n = 1 - The
roundloop doesn't run at all, and it returns[0]. - Using
1e9instead ofInfinity - An unreached node's edge could then turn
1e9 - 6into a price that looks real. KeepInfinity, or keep the skip on line 7.
Variations and follow-ups
Twists interviewers add
Finding the negative loop itself
Keep prev[to] = from whenever a price drops. When the final check finds an edge that still improves, set that price and step back through prev n times: you're now surely on the loop. Keep stepping until you return to the same node to list it.
The queue version (SPFA)
Only nodes whose price changed can improve their neighbors, so keep those nodes in a queue and relax only their edges. It's often much faster on real graphs, but the worst case is still O(n · e).
Exchange rate loops
To check whether trading currencies in a loop makes money, use the weight -Math.log(rate) for each exchange. Multiplying rates becomes adding logarithms, and a loop that multiplies to more than 1 becomes a negative loop.
Systems of difference rules
Rules like x[j] - x[i] <= c become edges i → j with weight c. Add a start with 0-cost edges to every variable and run Bellman-Ford: the dist values satisfy every rule, and a negative loop means the rules can't all be true.
In an interview
Spot it, explain it, answer follow-ups
- Spot it from negative costs (refunds, gains, credits), "detect a loop that makes the cost fall forever", or "at most
kstops". Those three phrases rule out Dijkstra. - Explain the round count out loud: "after round
k, every cheapest path with up tokedges is right, and a cheapest path has at mostn - 1edges". It justifies both the loop bound and the negative-loop check. - Test by hand a graph with the edges listed backwards, like the trace, and a three-node loop with a negative total. The first checks the number of rounds; the second checks the
nullreturn. - Expect "what's the cheapest flight with at most
kstops?". Answer: runk + 1rounds and, in each round, read prices from a copy ofdistmade before the round, so one round can't chain two edges.
Where frontend code uses it
The same idea in real UI code
- Pricing and checkout flows that combine discounts and fees as edge costs, where some steps lower the total, need an algorithm that accepts negative values.
- Currency or credit dashboards that check exchange rates for loops that turn a balance into more of itself, which is a negative-cycle check on the logarithms of the rates.
- Some distance-vector network routing protocols, which route traffic between servers, are based on the same repeated relaxing idea.