AlgoMaster Logo

All Paths From Source to Target

mediumFrequency7 min readUpdated June 23, 2026

Understanding the Problem

We have a directed acyclic graph represented as an adjacency list, and we need to find every possible path from node 0 to node n - 1, not one shortest path but the complete set.

Since the graph is a DAG, there are no cycles, so every traversal terminates. The absence of cycles also means a traversal cannot loop back to a node within a single path, so no "visited" set is needed to guarantee termination. A global visited set would be wrong here: the same node can appear in multiple valid paths, and marking it visited after the first path would suppress the others.

This is a backtracking problem on a graph. We start at node 0, explore each neighbor, and record the current path whenever we reach node n - 1. Because the graph is acyclic, every branch of the search bottoms out.

Key Constraints:

  • 2 <= n <= 15 -> The graph is small enough that exponential output is acceptable. A fully connected DAG has up to 2^(n-2) paths from node 0 to node n-1, which is 8192 for n = 15.
  • graph[i][j] != i -> No self-loops.
  • The input graph is guaranteed to be a DAG -> No cycles, so DFS terminates without a visited set.

Approach 1: DFS with Backtracking

Intuition

Depth-first search enumerates paths directly. Start at node 0, pick a neighbor, go deeper, pick another neighbor, go deeper again. Reaching node n - 1 completes a path. After finishing a branch, backtrack and try the next neighbor.

The only state we maintain is a single "current path" list: append a node when descending into it, remove it when backtracking. Since the DAG guarantee rules out cycles, the recursion cannot revisit a node within the current path and no visited set is needed.

Algorithm

  1. Create a result list to store all complete paths.
  2. Start a DFS from node 0 with the current path initialized to [0].
  3. At each node, check if it is the target node n - 1. If yes, add a copy of the current path to the result.
  4. Otherwise, iterate through all neighbors of the current node. For each neighbor, add it to the current path, recurse, then remove it (backtrack).
  5. Return the result list after DFS completes.

Example Walkthrough

1Start DFS from node 0, path = [0]
0current4312
1/6

Code

The same enumeration can also run breadth-first, with the queue holding whole paths instead of single nodes.

Approach 2: BFS with Path Tracking

Intuition

A standard BFS queue holds single nodes, which is enough for reachability or shortest distance. To output complete paths, each queue entry must carry the entire path from node 0 to its last node.

So each entry in the queue is a full path. When we dequeue a path whose last node is the target, we add it to the result. Otherwise, we extend the path by appending each neighbor of the last node and enqueue the extended copies. No visited set appears here either, for the same reason as in DFS: a node can belong to many paths. Replacing the queue with a stack gives an iterative depth-first version of the same idea; the paths come out in a different order.

Algorithm

  1. Initialize a queue with a single path [0].
  2. Create a result list for complete paths.
  3. While the queue is not empty, dequeue a path.
  4. Check the last node in the path. If it is the target n - 1, add the path to the result.
  5. Otherwise, for each neighbor of the last node, create a new path by appending the neighbor, and enqueue it.
  6. Return the result list.

Example Walkthrough

1Start: queue = [[0]].
0start123
1/7

Code

BFS stores full path copies in the queue, which costs far more memory than the DFS recursion stack. Both approaches also re-traverse the subgraph below a node once for every way of reaching that node. Caching sub-paths removes that repeated traversal.

Approach 3: DFS with Memoization

Intuition

In a DAG, the set of paths from any intermediate node X to the target is the same regardless of how we arrived at X. If nodes 1 and 2 both have edges to node 3, the paths from node 3 to the target are identical in both cases; only the prefix differs.

So instead of recomputing paths from node 3 to the target on every visit, cache them. The first call to allPaths(3) computes and stores every path from 3 to the target. Later calls return the cached list, and each caller prepends its own node to every cached sub-path.

This is top-down dynamic programming on the DAG. Acyclicity guarantees the recursion has no circular dependencies. The same computation can run bottom-up by processing nodes in reverse topological order and filling the table from the target backwards; the cached contents come out identical.

Algorithm

  1. Create a memo dictionary that maps each node to a list of paths from that node to the target.
  2. Define a recursive function allPaths(node) that returns all paths from node to the target.
  3. If node is already in the memo, return the cached result.
  4. Base case: if node is the target, cache and return [[target]].
  5. Otherwise, for each neighbor of node, recursively get all paths from neighbor to the target. For each such path, prepend node to it and add it to the result.
  6. Cache the result in the memo and return it.
  7. Call allPaths(0) to get the final answer.

Example Walkthrough

1allPaths(0) starts. First neighbor is 4, so call allPaths(4).
0current4recurse312
1/7

Code