Find all paths between two graph nodes
Master System Design with Codemia
Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.
Introduction
Finding every path between two graph nodes is useful for dependency analysis, route inspection, and workflow debugging when a single shortest path is not enough. The standard technique is depth-first search with backtracking, but path enumeration can grow explosively, so practical implementations need cycle control and often need limits as well.
Decide What Counts as a Path
Before writing code, define the path semantics clearly:
- directed or undirected graph
- simple paths only, or repeated nodes allowed
- maximum path length, if any
Most implementations enumerate simple paths, meaning a node cannot appear twice in the same candidate path. That avoids infinite loops on cyclic graphs and usually matches what users mean by “all paths.”
Recursive DFS With Backtracking
The clearest implementation uses depth-first search. Keep track of the current path and the nodes visited on the current branch, then backtrack when you return from recursion.
This works well for simple path enumeration in a directed graph and is usually the best version to understand first.
Iterative Search Avoids Recursion Limits
If the graph can be deep enough to threaten recursion depth, switch to an explicit stack. The logic stays similar, but the runtime stack is replaced by your own data structure.
This form is often easier to instrument with counters, timeouts, or early termination rules.
Add Limits for Real Systems
The number of paths between two nodes can be exponential in the size of the graph. That means “find all paths” is sometimes an impossible request at production scale. If the graph is user-supplied or dense, protect the search with depth and count limits.
These limits do not change the fundamental complexity, but they keep the query bounded enough to survive in an API or service.
Stream Results Instead of Storing Everything
If the consumer can process paths one at a time, yield them lazily rather than storing every path in memory first.
This is useful when the caller only needs the first few paths or wants to stop after inspecting enough evidence.
Test Boundary Cases Explicitly
Graph path code breaks most often on edge conditions. Good tests should cover:
- no path exists
- a cycle is present
start == target- multiple branching routes
Those tests catch missed copy operations, bad cycle checks, and incorrect base cases early.
Common Pitfalls
The main mistake is forgetting to prevent revisiting nodes, which causes infinite traversal on cyclic graphs. Another is storing the mutable path list directly without copying it when a path is found. Teams also underestimate how quickly the number of valid paths can explode, then discover too late that an unbounded all-path query is not safe for production.
Summary
- Use depth-first search with backtracking to enumerate simple paths.
- Add cycle control so graphs with loops do not recurse forever.
- Prefer an iterative stack if recursion depth is a concern.
- Bound search depth or path count in production-facing systems.
- Stream results when possible to reduce memory pressure.

