Choosing the Right Shortest‑Path Algorithm in NetworkX: Dijkstra, Bellman‑Ford, or A*
Learn how to pick the right shortest‑path algorithm in NetworkX – Dijkstra, Bellman‑Ford, or A* – with a clear example, performance tips, and common pitfalls to avoid in production.
16 Feb 2026, 14:31 UTC

Choosing the Right Shortest‑Path Algorithm in NetworkX
When you need a shortest path in a weighted graph, NetworkX gives you three high‑level helpers: shortest_path (Dijkstra by default), bellman_ford_path, and astar_path. The decision is simple if you know the weight constraints and the scale of the problem.
Dijkstra (via shortest_path)
Dijkstra is fast – O((V+E) log V) with a binary heap – but it refuses to work with negative edge weights. If you pass a graph that contains a negative edge, NetworkX raises NetworkXError or, in older releases, silently returns a wrong path. Use it when all edges are non‑negative and you want the fastest single‑source or single‑target query.
Bellman‑Ford (bellman_ford_path)
Bellman‑Ford handles negative weights and detects negative cycles reachable from the source. It runs in O(V·E), so it is slower than Dijkstra but still acceptable for moderate‑sized graphs where negative edges appear. If a negative cycle is found, it raises NetworkXUnbounded. Use it when your data may contain negative weights but you need guarantees of optimality.
A* (astar_path)
A* is Dijkstra with a heuristic. The user supplies a callable h(u, v) that estimates the cost from node u to the target v. The heuristic must be admissible (never over‑estimates) to preserve optimality. NetworkX does not check admissibility; an inadmissible heuristic simply yields a sub‑optimal path. Use A* when you have a cheap, admissible heuristic that can prune the search space.
Concrete Example
Below is a minimal graph that demonstrates all three algorithms. Run it in a Python interpreter where networkx is installed.
import networkx as nx
# Build a directed graph with a negative edge but no negative cycle
G = nx.DiGraph()
G.add_edge('A', 'B', weight=2)
G.add_edge('B', 'C', weight=3)
G.add_edge('A', 'C', weight=-4) # negative edge
G.add_edge('C', 'D', weight=1)
# Dijkstra – will raise an error because of the negative edge
try:
path_dij = nx.shortest_path(G, 'A', 'D', weight='weight')
except nx.NetworkXError as e:
print('Dijkstra error:', e)
# Bellman‑Ford – handles the negative edge
path_bf = nx.bellman_ford_path(G, 'A', 'D', weight='weight')
print('Bellman‑Ford path:', path_bf)
# A* with a zero heuristic (reduces to Dijkstra)
path_astar = nx.astar_path(G, 'A', 'D', heuristic=lambda u, v: 0, weight='weight')
print('A* path (zero heuristic):', path_astar)
# Reconstruct edge list from the node path
edge_list = list(zip(path_bf, path_bf[1:]))
print('Edge list:', edge_list)
Expected output (order may vary):
Bellman‑Ford path: ['A', 'C', 'D']
A* path (zero heuristic): ['A', 'C', 'D']
Edge list: [('A', 'C'), ('C', 'D')]
Performance in Production
- Single‑source queries:
single_source_dijkstra_path_lengthorsingle_source_bellman_ford_path_lengthavoid repeated heap construction. - Repeated all‑pairs on static graphs:
floyd_warshall(O(V³)) orjohnson(O(V² log V + V·E)) give you all distances in one shot. Johnson is preferable for sparse graphs with negative weights. - Large scale graphs: NetworkX’s dict‑of‑dict representation uses ~72 bytes per node and ~200 bytes per edge. For >10 M edges consider graph‑tool or SciPy’s sparse matrix routines (
scipy.sparse.csgraph.dijkstra). - Custom Dijkstra: Extract adjacency with
G.adjand use Python’sheapqfor tighter loops. This can be 2–3× faster than the built‑in wrapper when you run many queries on the same graph.
Common Mistakes and How to Avoid Them
- Wrong weight key: The
weightparameter is the attribute name, not a fixed string. Verify withlist(G.edges(data=True))[:3]before calling. - Heuristic signature:
h(u, v)expects both nodes. Writinglambda u: 0orlambda u, v: 0will raiseTypeErroror produce incorrect paths. - MultiGraph path loss: In
MultiGraphorMultiDiGraph, the shortest‑path functions consider only the minimum‑weight parallel edge. If you need the specific edge key, transform the graph to a simple graph or write custom logic. - Negative cycle detection scope:
bellman_ford_pathonly checks cycles reachable from the source. Usenegative_edge_cyclefor a global scan. - Exception handling:
NetworkXNoPathis raised when the target is unreachable. Catch it instead of pre‑checking withhas_path, which would double‑traverse.
Limitations
- All path functions return a list of nodes, not edges. Reconstruct edges with
zip(path, path[1:])if you need attributes. - NetworkX does not enforce admissibility of A* heuristics; suboptimal paths can result silently.
- Memory overhead grows linearly with edges; large graphs may exceed available RAM.
Practical Check
After running a shortest‑path function, confirm the path length matches the expected weight sum:
length = sum(G[u][v].get('weight', 1) for u, v in zip(path, path[1:]))
print('Total path weight:', length)
If the sum differs from nx.path_weight(G, path, weight='weight'), you likely passed the wrong weight key or the graph’s edge attributes were altered.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.