Home › DSA Patterns › Shortest Paths: Dijkstra and Bellman-Ford
S

Shortest Paths: Dijkstra and Bellman-Ford

Master two algorithms and you can solve almost every weighted-graph interview question thrown at you. I have watched strong candidates freeze on these, not because the algorithms are hard, but because they never learned to recognize the problem hiding under a word puzzle. The problem never says "shortest path." It says "cheapest flight with at most 2 layovers" or "least effort to climb across this grid," and the candidate starts writing BFS. Let me fix that.

DSA PatternsHard11 min read
Who asks this, and how oftenInterview frequency: High

Most asked at Google. Here is how often this pattern shows up in each company's coding rounds.

Google
86
Amazon
82
Uber
80
Meta
74
Microsoft
70
Relative frequency index from 0 to 100, based on commonly reported interview experiences. Use it as a guide to where this pattern pays off most, not an exact count.

If you remember five things

The whole pattern in one glance. Everything below is the defense of these five calls.

  1. It is always relaxation. Keep a best-known distance per node and ask "is going through u cheaper for v?" Every shortest-path algorithm is just a different order and count of that one check.
  2. Triage in three questions. All weights equal? Plain BFS. All non-negative? Dijkstra. Negatives or a hop limit? Bellman-Ford.
  3. Dijkstra is greedy, with a stale guard. Expand the closest unfinalized node, lock it on pop (never on push), and skip stale heap entries instead of fighting decrease-key.
  4. Bellman-Ford is brute force. Relax every edge V-1 times, then one extra pass flags a negative cycle. It is the only one here that handles negatives or a hop cap.
  5. Least weight, not fewest edges. The cheapest path can take more hops. That one confusion is what makes people wrongly reach for BFS.

1The mental model (what this really is)#

One idea runs through both algorithms. Get it and the rest is bookkeeping.

The shared goal.

  • Find the cheapest way from a start point to everywhere else (or to one target).
  • "Cheap" means the sum of edge weights along a path.
  • Picture cities joined by roads, each road a toll, start in one city, find the minimum total toll to reach each other city.
  • That is single-source shortest path. Both Dijkstra and Bellman-Ford solve exactly this. They differ in how, and in which graphs they tolerate.

The one operation underneath both: relaxation.

  • Keep a best-known distance to every node, starting at infinity for all except the source, which is 0.
  • Look at an edge (u, v) with weight w and ask: is going to u then crossing to v cheaper than the current best for v?
  • If yes, update v. Done in the right order enough times, this produces the answer.
if dist[u] + w < dist[v]:
    dist[v] = dist[u] + w
The one sentence to carry

Every shortest-path algorithm is just a different argument about the smartest order to do these relaxations, and how many you need. Dijkstra argues "closest first, once each." Bellman-Ford argues "everything, V-1 times." That is the whole difference.

The two tools, side by side.

  • Dijkstra: greedy. Always expand the closest unfinalized node next. Once a node leaves the heap, its distance is locked forever. Fast. Breaks the instant a negative edge appears.
  • Bellman-Ford: brute force. Relax every edge, repeat V-1 times. Slower by up to a factor of V, but it handles negative weights and detects negative cycles, which Dijkstra cannot do at all.

2How to recognize it (the signals in a problem)#

The problem rarely says "shortest path." It hides. Here is what I train people to listen for.

Phrasings that mean weighted shortest path:

  • "Minimum cost / time / effort to get from A to B" across a network of connections with per-step costs.
  • "Cheapest flight," "shortest travel time," "minimum cost to reach," with numbers on the edges.
  • A grid where each cell has a cost or weight and you move between neighbors. A weighted grid is just Dijkstra on an implicit graph where you never build the adjacency list.
  • "Best possible rate / highest probability / least weakness" (maximize, not minimize). Same engine, flipped comparison.
  • Negative values, debts, refunds, discounts, or losses on edges. Now you are in Bellman-Ford territory, and currency-arbitrage phrasing is the tell for negative cycles.

The fastest triage, in order:

  1. All weights equal (or 1)? Plain BFS already gives shortest path, O(V + E), no heap. Do not bring a heap to a BFS fight.
  2. All weights non-negative? Reach for Dijkstra.
  3. Any weight negative, or a hop limit? Bellman-Ford (or its queue-based cousin, SPFA).
From the interviewer's chair

Say that three-question check out loud before you write a line. When I hear a candidate name the weight constraint and pick the tool on purpose, I relax. When someone opens a heap with no idea whether the weights are negative, I start planting a single negative edge in the test cases to see if they ever notice.

Pause: the problem says "cheapest flight from city 0 to city N with at most 3 stops." You reach for Dijkstra. Why might that return a wrong answer?
Reveal the answer
Dijkstra finalizes a node the moment it pops, before it knows how many hops you are allowed to spend. The cheapest path by total weight might use too many stops, and once Dijkstra locks that node it will never reconsider a pricier-but-shorter-hop route. A hop constraint changes the problem. Use Bellman-Ford limited to K+1 passes, or a layered BFS carrying (node, stops). This exact trap is section 5, and it is one of the most common ways people lose the round.

3The template (how it works, with pseudocode)#

Two templates, memorized cold. Write them from muscle memory and spend your thinking time on the problem, not the plumbing.

Dijkstra with a min-heap. I can write this shape in about 90 seconds, and so should you.

def dijkstra(graph, source, n):
    dist = [infinity] * n
    dist[source] = 0
    heap = [(0, source)]          # (distance, node)

    while heap:
        d, u = pop_min(heap)
        if d > dist[u]:           # stale entry, skip it
            continue
        for (v, w) in graph[u]:
            if d + w < dist[v]:
                dist[v] = d + w
                push(heap, (d + w, v))
    return dist

Two lines earn their keep.

  • The if d > dist[u]: continue guard lets you skip decrease-key entirely.
  • Instead of fighting the heap to update an existing entry, push a duplicate (new_dist, v) and skip the old one when it surfaces.
  • The heap can grow to O(E) entries this way, which is fine, and every interviewer I know accepts it.
  • It is cleaner than any decrease-key you will write under pressure.

Bellman-Ford. No heap, just loops over the edge list.

def bellman_ford(edges, source, n):
    dist = [infinity] * n
    dist[source] = 0

    for i in range(n - 1):        # V-1 passes
        changed = False
        for (u, v, w) in edges:
            if dist[u] != infinity and dist[u] + w < dist[v]:
                dist[v] = dist[u] + w
                changed = True
        if not changed:
            break                 # early exit, nothing left to improve

    # one more pass to detect a negative cycle
    for (u, v, w) in edges:
        if dist[u] != infinity and dist[u] + w < dist[v]:
            return None           # negative cycle reachable from source
    return dist

The details that keep it correct.

  • The dist[u] != infinity check matters: without it, infinity + w can wrap or falsely relax an unreached node, and you get phantom paths. Add it and move on.
  • Why V-1 passes? The longest simple shortest path touches at most V nodes, so it has at most V-1 edges.
  • Each full pass locks in one more edge of that path, in order. After V-1 passes, every shortest path is settled.
  • If a V-th pass still improves something, a negative cycle exists, because only an endless loop of discounts could keep lowering the cost below any floor.
Red flag that fails you here

Marking a node visited when you push it instead of when you pop it. You finalize a node only when it leaves the heap. Marking it on push can lock in a non-minimal distance, because a cheaper route might still be sitting deeper in the heap. This is a silent correctness bug, and it is exactly the kind of thing I probe by handing you a graph where the greedy order and the first-seen order disagree.

4A worked example (walk through one problem step by step)#

Let me run Dijkstra on a tiny graph so you see the greedy order in motion. Nodes A, B, C, D. Edges (directed, with weights):

  • A -> B (4)
  • A -> C (1)
  • C -> B (2)
  • B -> D (1)
  • C -> D (5)
graph LR A -->|4| B A -->|1| C C -->|2| B B -->|1| D C -->|5| D
Watch the two routes to D compete. The direct-looking A to B to D costs 5, but A to C to B to D costs 4. More hops, less weight. That gap is the whole lesson of this trace.

Source is A. I want the shortest distance to every node.

Start: dist = {A:0, B:inf, C:inf, D:inf}. Heap: [(0, A)].

Pop (0, A). It is fresh. Relax A's edges.

  • A -> B: 0 + 4 = 4 < inf. Set dist[B] = 4, push (4, B).
  • A -> C: 0 + 1 = 1 < inf. Set dist[C] = 1, push (1, C).

Heap now: [(1, C), (4, B)].

Pop (1, C). This is the greedy heart of it. C is the closest unfinalized node, so Dijkstra commits to dist[C] = 1 as final. Nothing will ever beat it, because every other route to C would have to pass through a node already at distance greater than 1. Relax C's edges.

  • C -> B: 1 + 2 = 3 < 4. Better path to B through C. Set dist[B] = 3, push (3, B).
  • C -> D: 1 + 5 = 6 < inf. Set dist[D] = 6, push (6, D).

Heap: [(3, B), (4, B), (6, D)]. Notice there are two entries for B now. That stale (4, B) is harmless. We will skip it later, and this is exactly why we push duplicates instead of doing decrease-key.

Pop (3, B). Matches dist[B] = 3, fresh. Relax.

  • B -> D: 3 + 1 = 4 < 6. Better. Set dist[D] = 4, push (4, D).

Heap: [(4, B), (4, D), (6, D)].

Pop (4, B). But dist[B] is already 3, and 4 > 3. Stale. Skip. This is exactly what that guard line protects against. Without it, we would reprocess B's edges for nothing.

Pop (4, D). Matches dist[D] = 4, fresh. D has no outgoing edges. Nothing to relax.

Pop (6, D). Stale (6 > 4). Skip. Heap empty. Done.

What the trace proves:

  • Final: A:0, C:1, B:3, D:4.
  • The shortest path to D is A -> C -> B -> D, total 4, even though it takes three hops.
  • The direct-looking A -> B (4) plus B -> D (1) gives 5, which is worse.
  • Shortest path means least weight, not fewest edges. That trips people up constantly, and it is the exact confusion that makes people reach for BFS when they should not.

5Variations and gotchas#

The interviewer rarely hands you textbook Dijkstra. Here are the costumes it wears.

  • Dijkstra on a grid. No explicit adjacency list. The graph is implicit: neighbors are the up/down/left/right cells (sometimes 8-directional). Generate edges on the fly inside the loop. Same algorithm, same heap, just bounds-check the coordinates.
  • Modified objective. "Path with the minimum maximum edge" (minimize the single largest weight you cross) or "path maximizing the minimum" (widest path). Keep Dijkstra's structure but change the relaxation: instead of d + w, use max(d, w) or min(d, w). The heap just sorts by the new cost. This shows up far more than people expect, and it is the whole trick behind Swim in Rising Water and Path With Minimum Effort.
  • Probabilities / multiplication. "Maximum probability path." Weights are in 0 to 1 and they multiply instead of add, and you maximize instead of minimize. Use a max-heap and multiply along edges. It is Dijkstra wearing a costume; the greedy proof still holds because multiplying by a value at most 1 never increases probability.
  • At most K stops / steps. The cheapest-flights-within-K-stops problem. Plain Dijkstra can return a wrong answer here, because the cheapest path by weight might use too many hops, and Dijkstra finalizes a node before it knows how many hops you will be allowed. Use Bellman-Ford limited to K+1 passes, or a BFS by layers carrying (node, stops). The rule: if there is a hop constraint, lean Bellman-Ford.
  • SPFA. A queue-based Bellman-Ford that only reprocesses a node when its distance actually changed. Much faster on typical inputs, same worst case. Worth naming if negatives appear and you want speed, but be honest that its worst case is still O(VE) and adversarial graphs can hit it.
  • 0-1 BFS. If every weight is 0 or 1, skip the heap. Use a deque: push 0-weight moves to the front, 1-weight moves to the back. O(V + E), no log factor. Saying this out loud signals real depth, and it is the intended solution to Minimum Obstacle Removal and Minimum Cost to Make at Least One Valid Path.
The gotcha that silently breaks everything

Dijkstra and negative edges do not mix. The greedy promise ("once I finalize a node, its distance is settled") depends on never finding a cheaper route to it later. A negative edge can do exactly that, so the proof collapses and the output is silently wrong. If weights can be negative, do not use Dijkstra no matter how tempting the speed is. Reach for Bellman-Ford.

6Complexity#

Algorithm Time Space Use when
BFS O(V + E) O(V) All edges equal weight
0-1 BFS O(V + E) O(V) Weights are 0 or 1
Dijkstra (binary heap) O((V + E) log V) O(V) Non-negative weights
Bellman-Ford O(V ยท E) O(V) Negative weights, or hop limits, or cycle detection

Where the bounds come from:

  • The log V factor in Dijkstra comes from heap operations, and we may push up to O(E) entries, which is where the E in that bound comes from.
  • For dense graphs where E approaches V squared, a simple array-based Dijkstra that scans for the minimum each round runs in O(V squared) and actually beats the heap version, since O(V squared + E) with E near V squared is just O(V squared) while the heap version pays an extra log V.
  • Nice to know, rarely needed in interviews, but a clean answer if an interviewer presses on dense graphs.

7Mistakes that cost people the round#

  • Reaching for Dijkstra with negative edges. Instant red flag to the interviewer, and they often plant a single negative weight on purpose to see if you check. Always ask or confirm the weight constraint before you commit to an algorithm.
  • Forgetting the stale-entry guard. Without if d > dist[u]: continue, you reprocess finalized nodes, inflate your runtime, and sometimes it still returns the right answer, which is worse, because it hides the bug until a harder test case.
  • Confusing fewest edges with least weight. BFS gives fewest edges. If weights vary, BFS is simply wrong, even though it compiles and returns a number that looks plausible.
  • Using plain Dijkstra on "at most K stops." The classic trap. The hop limit changes the problem, and plenty of people lose the offer right here by finalizing a node too early.
  • Not handling unreachable nodes. Some targets stay at infinity. Decide what to return for those (usually -1) and say it out loud. Interviewers watch for this edge case specifically.
  • Marking a node visited when you push it, instead of when you pop it. You finalize a node only when it leaves the heap. Marking it visited on push can lock in a non-minimal distance, because a cheaper route might still be sitting deeper in the heap.
  • Skipping negative-cycle detection when the problem clearly hints at one. If they mention arbitrage, currency loops, refunds that compound, or "can the cost decrease forever," they want that V-th pass and a None or false return.

Pick the algorithm from the weight constraint, not from reflex.

8Your practice path (named problems by difficulty)#

Do them in this order. Each one teaches a specific muscle, and I have noted which.

Warm up (recognize the pattern):

  • Network Delay Time (textbook single-source Dijkstra, build confidence)
  • Path With Minimum Effort (Dijkstra with max relaxation on a grid)

Core (the real interview zone):

  • Cheapest Flights Within K Stops (the hop-limit trap, Bellman-Ford with K+1 passes shines)
  • Path with Maximum Probability (flipped objective, max-heap, multiply along edges)
  • Swim in Rising Water (minimize the maximum, grid Dijkstra)
  • The Maze II (weighted implicit graph, distance rolls until it hits a wall)

Stretch (level up):

  • Minimum Cost to Make at Least One Valid Path in a Grid (0-1 BFS)
  • Find the City With the Smallest Number of Neighbors at a Threshold Distance (all-pairs, meet Floyd-Warshall at O(V cubed))
  • Reachable Nodes In Subdivided Graph (Dijkstra with a twist on counting subdivided edge nodes)
  • Minimum Obstacle Removal to Reach Corner (another 0-1 BFS in disguise)

If you can look at each of these and name the right tool in under 30 seconds, you are interview-ready. That recognition speed is the actual skill being tested, not your ability to recite the heap pop.

9Where this shows up#

  • Shortest path is a staple of the hard graph rounds at Google, Amazon, and Meta.
  • It is common in the on-sites at Uber, Lyft, and DoorDash, plus anyone doing routing, logistics, or maps.
  • Trading and fintech shops love Bellman-Ford because of the currency-arbitrage framing, where a negative cycle literally means free money and the V-th pass is the whole point of the question.
  • Infrastructure and systems teams favor the grid and network-delay variants, since those map onto packet routing and latency.
  • If a company does anything with maps, delivery, or networks, expect at least one shortest-path question somewhere in the loop.
Follow-ups the interviewer will ask (and your answers)
"Why not just always use Bellman-Ford if it handles more cases?"
Speed. Bellman-Ford is O(V times E); Dijkstra is O((V + E) log V), which is dramatically faster on the non-negative graphs that make up most problems. I pay for the generality only when I need it, meaning negatives or a hop cap. On a non-negative graph, reaching for Bellman-Ford is leaving a big constant factor on the table for no reason.
"Your heap can hold stale entries. Doesn't that blow up memory and time?"
The heap grows to O(E) entries, and each is a pop plus a possible push, so the bound stays O((V + E) log V). The stale guard `if d > dist[u]: continue` makes every stale pop O(1) work. I trade a little extra memory for skipping decrease-key, which is cleaner to write and just as correct. It is the standard interview-grade Dijkstra.
"At most K stops: walk me through why plain Dijkstra fails and what you'd do."
Dijkstra finalizes a node on pop, before it knows how many hops remain. The cheapest path by weight may exceed K stops, and once the node is locked a pricier-but-fewer-hop route can never replace it. I use Bellman-Ford capped at K+1 passes: each pass relaxes one more edge layer, so after K+1 passes I have the cheapest path using at most K+1 edges, which is exactly K stops. I relax off a snapshot of the previous pass so updates within a pass do not leak extra hops.
"The graph has negative edges but no negative cycle. Any faster option than Bellman-Ford?"
Johnson's algorithm reweights every edge to non-negative using one Bellman-Ford pass from a virtual source, then runs Dijkstra from each node. That is the standard move for all-pairs on a sparse graph with negatives. For a single source with negatives, SPFA (queue-based Bellman-Ford) is much faster on typical inputs, though its worst case is still O(V times E).
"How do you detect the negative cycle and recover the nodes it poisons?"
Run the extra pass after V-1 rounds. Any edge that still relaxes sits on or downstream of a negative cycle. To mark every poisoned node, keep relaxing from the ones found in that V-th pass (a BFS or DFS over successors) and tag them as negative-infinity. Distances to those nodes are undefined because you can loop forever to drive the cost lower.

What you can now explain in the room

  • Why relaxation is the single operation under every shortest-path algorithm.
  • The three-question triage that picks BFS, Dijkstra, or Bellman-Ford before you write a line.
  • Why Dijkstra finalizes on pop, not push, and why the stale guard beats decrease-key.
  • Why Bellman-Ford needs V-1 passes and how the V-th pass detects a negative cycle.
  • Why a negative edge silently breaks Dijkstra's greedy proof.
  • How the same heap solves grid, max-min, probability, and hop-limited variants with a tweaked relaxation.

Want to see exactly which companies lean on this pattern and in which rounds? Check the company guides for the real breakdown.

Get a prep plan built around your Shortest Paths: Dijkstra and Bellman-Ford interview

Tell us the role and your interview date. We will send back a dated, day by day plan. Free.

Thanks. Check your inbox, your Shortest Paths: Dijkstra and Bellman-Ford prep plan is on the way.