Skip to content

Shortest Paths

intermediate · 30 min · Asked often · Last reviewed 31 Oct 2026 · by TheLearningLoop Team

Key takeaways

  • Every shortest path algorithm relaxes edges: if dist[u] + w is less than dist[v], update dist[v]. They differ in the order.
  • Dijkstra is fastest but needs non-negative weights, because it assumes a finalised vertex never improves. Bellman-Ford handles negative edges and detects negative cycles in O(V × E).
  • Floyd-Warshall solves all pairs in O(V³) with three loops. A negative number on its diagonal means a negative cycle.

New to this topic? Read the overview first: Data Structures & Algorithms: The Big Picture

When to use a shortest path algorithm

Use a shortest path algorithm when you need:

  • The cheapest route between places where edges have different costs: road distance, flight price, network latency.
  • The cheapest route when some costs are negative, such as a trade that earns money or a refund: use Bellman-Ford.
  • The distance between every pair of vertices in a small graph, for example a table of travel times between 300 depots: use Floyd-Warshall.
  • To detect a negative cycle, such as a currency loop that makes money from nothing (arbitrage): Bellman-Ford or Floyd-Warshall.

Use something else when:

  • Every edge costs the same (or the graph has no weights): plain BFS finds the fewest edges in O(V + E).
  • All weights are zero or one: use 0-1 BFS with a double-ended queue.
  • The graph is a DAG (no cycles): relax the edges in topological order for O(V + E), even with negative weights.
  • You need a path to one target on a huge map with a good distance estimate: use A* instead of searching in every direction.

Do not write your own graph library in production. Use a tested one (see the real-world section). Writing these from scratch is for learning and interviews.

How shortest path algorithms work

All three algorithms rest on one operation, relaxing an edge. If you know the best distance to u, and there is an edge u → v of weight w, then going through u might beat the best known distance to v:

Relax u → v: if dist[u] + w < dist[v], set dist[v] = dist[u] + w.

They differ in the order they relax edges, which decides what they can handle. A priority queue (a min-heap) is a structure that always hands back the item with the smallest key, and Dijkstra uses one to find the closest unfinished vertex quickly.

  • Dijkstra always finalises the closest unfinished vertex next and relaxes its outgoing edges. Because nothing can be closer than the closest, that vertex's distance can never improve again. This is only true when no edge is negative.
  • Bellman-Ford does not trust any order. It relaxes every edge, V - 1 times. A shortest path has at most V - 1 edges, so after that many rounds every distance is correct. One extra round that still improves something proves a negative cycle.
  • Floyd-Warshall answers all pairs. It allows one more vertex k at a time as a middle stop and asks, for every pair, "is i → k → j shorter than the best so far?"

Try it

Choose an algorithm and a graph. The graph has four vertices A, B, C, D and starts from A.

Shortest Paths

Step 1 of 6 · Press Space to play, arrow keys to step

Vertex

Distance from A

Initial state. Edges: A→B (2), A→C (3), C→B (-2), B→D (1). Distance to A is 0 and every other distance is unknown (∞).

step:
0
edge checks:
0

Things to try:

  • Run Dijkstra on All weights positive. It finalises A, C, B, D and the distances are 0, 3, 2, 8 (B costs 3 through C, because 2 + 1 beats 4).
  • Run Dijkstra on One negative edge. B is finalised at 2, then the edge C → B (weight -2) would give 1, but Dijkstra ignores it. The distances are wrong. Run Bellman-Ford on the same graph and it gets A to B right: 1.
  • Run Bellman-Ford on A negative cycle. It reports the cycle. Then run Floyd-Warshall on the same graph and look for a negative number on the diagonal.

Shortest path operations: relax, pick the closest, repeat

Dijkstra. Keep a priority queue of unfinished vertices keyed by distance.

  1. Set every distance to infinity, except the start, which is 0.
  2. Take the unfinished vertex with the smallest distance and mark it final.
  3. Relax every edge leaving it. Repeat from step 2 until the queue is empty.

Bellman-Ford.

  1. Set every distance to infinity, except the start.
  2. Repeat V - 1 times: relax every edge. Stop early if a whole round changes nothing.
  3. Do one more round. If any edge still improves, a negative cycle is reachable from the start.

Floyd-Warshall. Keep a matrix d[i][j] of best distances, starting with the direct edges. For k from 1 to V, for every i and j, set d[i][j] = min(d[i][j], d[i][k] + d[k][j]). A negative value on the diagonal means a negative cycle.

Dijkstra needs non-negative weights, and that is the question interviewers ask. After a vertex is finalised, a negative edge could still make it cheaper, so its answer would be wrong. Bellman-Ford pays more to avoid that assumption.

Dijkstra in code, with the path. Keep a prev array: each time a relaxation succeeds, record which vertex it came from. To print the route, walk prev back from the target and reverse.

import heapq

def dijkstra(n, adj, src=0):
    # adj[u] is a list of (v, weight) pairs; all weights must be >= 0.
    INF = float("inf")
    dist = [INF] * n
    prev = [None] * n
    dist[src] = 0
    heap = [(0, src)]                       # (distance, vertex)
    while heap:
        d, u = heapq.heappop(heap)
        if d > dist[u]:                     # stale entry: a better one was already processed
            continue
        for v, w in adj[u]:
            if d + w < dist[v]:             # relax u -> v
                dist[v] = d + w
                prev[v] = u
                heapq.heappush(heap, (dist[v], v))
    return dist, prev

def path_to(prev, target):
    path = []
    while target is not None:
        path.append(target)
        target = prev[target]
    return path[::-1]                       # a path of just [target] means no route if target != src

Worked trace on the "All weights positive" graph (edges A→B 4, A→C 2, C→B 1, B→D 5, C→D 8):

StepTake (final)Distances A, B, C, Dprev changes
Startnone0, ∞, ∞, ∞none
1A at 00, 4, 2, ∞B from A, C from A
2C at 20, 3, 2, 10B from C (2 + 1 beats 4), D from C
3B at 30, 3, 2, 8D from B (3 + 5 beats 10)
4D at 80, 3, 2, 8none

Walking prev back from D gives D, B, C, A, so the path is A → C → B → D with cost 8. Bellman-Ford and Floyd-Warshall can record the predecessor in the same way when a relaxation succeeds.

Edge cases to check.

  • Unreachable vertices keep distance ∞. Treat ∞ as "no route", and never add a weight to ∞ without checking (the Bellman-Ford code tests dist[u] != INF for this reason).
  • Parallel edges: keep the cheapest (Floyd-Warshall starts with min of the duplicates).
  • Stale heap entries: the heap may hold several entries for one vertex; skip any whose distance is larger than the current best, as the code above does.
  • Negative cycle in Floyd-Warshall: values on the cycle can keep shrinking and grow very negative, so check the diagonal for negatives after the run.
  • Zero-weight edges are fine for Dijkstra. Only negative weights break it.

Shortest path time complexity

AlgorithmTimeSpaceNegative edgesDetects negative cycle
Dijkstra (binary heap)O((V + E) log V)O(V)NoNo
Bellman-FordO(V × E)O(V)YesYes
Floyd-Warshall (all pairs)O(V³)O(V²)YesYes
BFS (unweighted)O(V + E)O(V)Not applicableNot applicable

V is the number of vertices and E the number of edges. Space is the extra memory beyond the graph itself; storing the graph as an adjacency list takes O(V + E) for every algorithm. Dijkstra's heap version is far faster than Bellman-Ford on large sparse graphs, which is why it is the default whenever weights are non-negative. Floyd-Warshall's cube is fine up to a few hundred vertices.

Shortest path algorithms vs other approaches

JobDijkstraBellman-FordFloyd-WarshallBFSDAG relaxation
One source, non-negative weightsO((V + E) log V)O(V × E)O(V³)Only if unweightedOnly on a DAG
One source, negative weightsWrongO(V × E)O(V³)NoO(V + E) on a DAG
Detect a negative cycleNoYesYesNoNot applicable
All pairsV runs of DijkstraV runs of Bellman-FordO(V³), simplestNoNo
Fewest edgesNoNoNoO(V + E)No
Easy to writeMediumEasyEasyEasyEasy

Here V is vertices and E edges. Dijkstra wins on large graphs with non-negative weights. Bellman-Ford wins when negative edges or cycle detection matter, and Floyd-Warshall wins for small all-pairs problems.

How to explain shortest paths in an interview

  • Start with the question "are there negative weights?" It decides everything: no negatives means Dijkstra, negatives means Bellman-Ford, and unweighted means BFS.
  • Describe relaxation in one sentence, and say that Dijkstra's greedy choice is valid only because no later path can be cheaper than the closest vertex.
  • Give complexity in V and E: Dijkstra O((V + E) log V), Bellman-Ford O(V × E), Floyd-Warshall O(V³).
  • Mention negative cycles: shortest paths are undefined, and Bellman-Ford's extra round finds them. In a currency problem, take the logarithm of rates to turn products into sums.

Where you meet shortest paths in real systems

Anything that routes, prices or schedules over a network is a shortest path problem. Here are three of them drawn out, the first with code you can run, then more examples.

Example 1: Finding arbitrage in currency exchange rates

A trading system holds exchange rates between currencies. A sequence of trades that ends with more money than it started with is an arbitrage. Take the negative logarithm of each rate and the product of rates becomes a sum of weights, so a profitable loop becomes a negative cycle. Bellman-Ford detects it. Dijkstra cannot, because the weights are negative. Open the example below to see the picture and the code.

Code: Bellman-Ford with negative cycle detectionTime O(V × E)Memory O(V)
ABCD
The edge from C to B costs -2. The cheapest route from A to B goes through C (3 - 2 = 1), not directly (2). Dijkstra would miss this; Bellman-Ford finds it.
# Bellman-Ford shortest paths from vertex 0, with negative edges allowed.
# Time: O(V * E): up to V-1 passes over all E edges.   Memory: O(V) for the distances.
INF = float("inf")

def bellman_ford(n, edges, src=0):
    dist = [INF] * n
    dist[src] = 0
    for _ in range(n - 1):                  # a shortest path has at most V-1 edges
        changed = False
        for u, v, w in edges:
            if dist[u] != INF and dist[u] + w < dist[v]:   # relax the edge
                dist[v] = dist[u] + w
                changed = True
        if not changed:                     # nothing improved, so we can stop early
            break
    # One more pass: if anything still improves, a negative cycle is reachable.
    negative_cycle = any(dist[u] != INF and dist[u] + w < dist[v] for u, v, w in edges)
    return dist, negative_cycle

# Vertices A, B, C, D = 0, 1, 2, 3. The edge C -> B has weight -2.
dist, neg = bellman_ford(4, [(0, 1, 2), (0, 2, 3), (2, 1, -2), (1, 3, 1)])
print("dist:", *dist)
print("negative cycle:", str(neg).lower())
# B -> C -> B costs -3 + 1 = -2 each time round, so distances shrink for ever.
_, neg = bellman_ford(4, [(0, 1, 1), (1, 2, -3), (2, 1, 1), (2, 3, 2)])
print("negative cycle:", str(neg).lower())

Each version runs Bellman-Ford from vertex A on the graph in the figure, then on a second graph that contains a negative cycle. Every version was compiled and run, printed the distances 0 1 3 2 and then negative cycle: false and negative cycle: true.

Example 2: Network routing

Routers in a network need the cheapest path to every destination. Link-state protocols such as OSPF make every router build a map of the whole network and run Dijkstra's algorithm on it. Older distance-vector protocols such as RIP work like a distributed Bellman-Ford: each router tells its neighbours its best distances and relaxes its own table with what it hears.

Learn the maplink costsRun Dijkstrafrom this routerKeep first hopsforwarding tableForward packetsto the next router
Each router learns the links and costs of the whole network, runs Dijkstra from itself, and keeps only the first hop of each shortest path in its forwarding table.

Example 3: Travel time tables between depots

A logistics company wants the travel time between every pair of its 200 depots, to plan deliveries. That is an all-pairs problem. Floyd-Warshall fills a 200 × 200 table with three nested loops, about 8 million checks, and then any pair is a lookup.

From A0429From B∞0∞5From C∞106From D∞∞∞0
Each cell is the best known time from the row depot to the column depot. Allowing B as a middle stop lowered some cells, shown highlighted. After every depot has been allowed, the table is final.

More real-world scenarios

4. Maps and navigation

Road maps are huge graphs with non-negative travel times. Navigation apps use Dijkstra-like searches, accelerated with A* and precomputed shortcuts, so a route appears in milliseconds.

5. Game pathfinding

Characters move across a grid or a navigation mesh where terrain has different costs (road, mud, water). A* is Dijkstra with a distance guess that points the search at the target.

6. Flight and travel booking

Finding the cheapest trip across airlines is a shortest path where edge weights are prices or times. Constraints such as a limit on the number of stops add a dimension to the search.

7. Currency and asset arbitrage

As in Example 1, a negative cycle in a graph of logarithms of exchange rates means a risk-free profit loop, so trading systems run Bellman-Ford style checks.

8. Project scheduling

Tasks with dependencies form a DAG. The longest path is the minimum project length (the critical path), found by relaxing edges in topological order in O(V + E).

9. Dependency and build ordering with costs

Build systems estimate the time to build a target as the longest path through its dependencies, using the same DAG relaxation as project scheduling.

10. Social network distance

"How many hops separate two people?" is BFS on an unweighted graph. If connections have strength or cost, it becomes Dijkstra.

11. Chip design and circuits

Signal delay through a circuit is a path problem with a cost per wire and gate. Timing tools look for the slowest path (the critical path) to set the clock speed.

12. Supply chain and shipping networks

Choosing routes and warehouses to minimise cost or time is a shortest path or, with capacities, a minimum-cost flow problem built from the same relaxation idea.

Language standard libraries

No mainstream standard library ships a general shortest-path function. You write it on top of a priority queue (for Dijkstra) or plain arrays (for Bellman-Ford and Floyd-Warshall), or use a graph package.

LanguagePriority queue for DijkstraGraph packageNotes
JavaPriorityQueueJGraphT (third party)PriorityQueue has no decrease-key, so push duplicates and skip stale entries.
C++std::priority_queueBoost Graph Library (third party)Use std::greater for a min-heap, or push negated distances.
C#PriorityQueue<TElement, TPriority> (.NET 6 and later)Third-party packagesSame duplicates-and-skip pattern.
RustBinaryHeap with std::cmp::Reversepetgraph crateBinaryHeap is a max-heap, so wrap the distance in Reverse.
Pythonheapqnetworkx (third party)networkx.shortest_path and bellman_ford_path cover the algorithms in this lesson.
Gocontainer/heapgonum.org/v1/gonum/graphcontainer/heap needs you to implement an interface.
JavaScriptNone built inThird-party packagesUse a small binary-heap class or a library.
KotlinPriorityQueue (Java's)JGraphT through JavaSame as Java.

The takeaway from the table: heaps are built in almost everywhere, shortest-path algorithms are not. Interviews expect you to write Dijkstra yourself on top of the language's heap.

The takeaway

Whenever a system must find the cheapest route through a network, look for a shortest path algorithm: Dijkstra when all costs are non-negative, Bellman-Ford when some are negative or cycles must be detected, Floyd-Warshall for small all-pairs tables. When every step costs the same, it usually just runs BFS.

Interview questions

Try answering out loud before you reveal hints. Interviewers care about how you reason, not just the final answer.

  1. 1.

    There are n nodes and directed edges with travel times. A signal starts at node k. How long until every node receives it?

    medium
  2. 2.

    Find the cheapest flight from a source to a destination with at most k stops.

    medium
  3. 3.

    Given currency exchange rates, how do you detect an arbitrage opportunity?

    hard
  4. 4.

    Why does Dijkstra's algorithm give wrong answers with negative edges?

    easy
  5. 5.

    After running Dijkstra, how do you return the actual route to a target and not just its cost?

    easy

Finished this lesson?

Progress is saved on this device. Create a free account to keep it everywhere.

Topics:GraphsShortest pathDijkstraBellman fordFloyd warshallNegative cycle

↑ Back to top

Next lesson