Breadth First Search
Depth First Search
Graph Traversal
Algorithm
Computer Science

Breadth First Search and Depth First Search

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

Breadth First Search and Depth First Search are the two core graph traversal strategies used in interviews and real systems alike. They both visit connected nodes efficiently, yet they produce different orderings and guarantees. Picking the right one depends on what answer you need, such as shortest path, cycle detection, or component discovery.

Core Difference: Queue Versus Stack Behavior

BFS explores neighbors level by level from a starting node. It uses a queue, so closer nodes are processed before farther nodes.

DFS explores one branch deeply, then backtracks. It uses recursion or an explicit stack.

That single design difference drives the main tradeoffs:

  • BFS finds shortest path by edge count in unweighted graphs.
  • DFS is convenient for tasks that depend on traversal state and backtracking.

If you remember one rule, remember this: BFS is nearest-first, DFS is depth-first.

BFS Example with Distances

The code below computes shortest hop distance from a source node to all reachable nodes.

python
1from collections import deque
2
3
4def bfs_distances(graph, start):
5    q = deque([start])
6    distance = {start: 0}
7
8    while q:
9        node = q.popleft()
10        for nxt in graph.get(node, []):
11            if nxt not in distance:
12                distance[nxt] = distance[node] + 1
13                q.append(nxt)
14
15    return distance
16
17
18if __name__ == "__main__":
19    graph = {
20        "A": ["B", "C"],
21        "B": ["D"],
22        "C": ["D", "E"],
23        "D": ["F"],
24        "E": [],
25        "F": []
26    }
27
28    print(bfs_distances(graph, "A"))

The first time BFS discovers a node, it has already found the minimum number of edges from the source in an unweighted graph.

DFS Example with Iterative Stack

This DFS uses an explicit stack to avoid recursion limits in deep graphs.

python
1def dfs_order(graph, start):
2    stack = [start]
3    visited = set()
4    order = []
5
6    while stack:
7        node = stack.pop()
8        if node in visited:
9            continue
10
11        visited.add(node)
12        order.append(node)
13
14        # Reverse only for deterministic demo ordering
15        for nxt in reversed(graph.get(node, [])):
16            if nxt not in visited:
17                stack.append(nxt)
18
19    return order
20
21
22if __name__ == "__main__":
23    graph = {
24        "A": ["B", "C"],
25        "B": ["D"],
26        "C": ["D", "E"],
27        "D": ["F"],
28        "E": [],
29        "F": []
30    }
31
32    print(dfs_order(graph, "A"))

DFS is a natural fit for branch-oriented exploration and algorithms that need entry and exit semantics.

Typical Use Cases in Practice

Use BFS when you need:

  • shortest path by number of edges
  • level-order expansion
  • nearest reachable target

Use DFS when you need:

  • cycle detection
  • topological sort style workflows
  • connected-component or strongly-connected analyses
  • path existence with backtracking logic

Both are often part of bigger algorithms. For example, topological sort and articulation point detection are DFS-heavy, while multi-source expansion and shortest steps in grids are BFS-heavy.

Complexity and Memory Profile

For adjacency-list graphs, BFS and DFS both run in O(V + E) time, where V is number of vertices and E is number of edges.

Memory behavior differs:

  • BFS uses a queue that can grow to graph frontier width.
  • DFS uses stack depth that can grow to traversal depth.

In very wide graphs, BFS can consume more memory. In very deep graphs, recursive DFS can hit recursion limits.

Reconstructing a Shortest Path with BFS

Distance values are useful, but real applications often need the actual path. Track parent pointers while traversing.

python
1from collections import deque
2
3
4def bfs_path(graph, start, goal):
5    q = deque([start])
6    parent = {start: None}
7
8    while q:
9        node = q.popleft()
10        if node == goal:
11            break
12
13        for nxt in graph.get(node, []):
14            if nxt not in parent:
15                parent[nxt] = node
16                q.append(nxt)
17
18    if goal not in parent:
19        return []
20
21    path = []
22    cur = goal
23    while cur is not None:
24        path.append(cur)
25        cur = parent[cur]
26
27    return list(reversed(path))

This is one of the most common practical BFS patterns.

DFS for Cycle Detection in Directed Graphs

DFS can detect directed cycles with color states.

python
1def has_directed_cycle(graph):
2    WHITE, GRAY, BLACK = 0, 1, 2
3    color = {n: WHITE for n in graph}
4
5    def visit(node):
6        color[node] = GRAY
7        for nxt in graph.get(node, []):
8            state = color.get(nxt, WHITE)
9            if state == GRAY:
10                return True
11            if state == WHITE and visit(nxt):
12                return True
13        color[node] = BLACK
14        return False
15
16    for n in graph:
17        if color[n] == WHITE and visit(n):
18            return True
19    return False

This pattern is concise and scales well to large dependency graphs.

Common Pitfalls

A frequent bug is forgetting a visited set, which creates infinite loops on cyclic graphs.

Another mistake is using DFS where shortest unweighted path is required. DFS can find a path, but not guaranteed shortest by edges.

Recursive DFS can fail on deep inputs due to call stack depth. Use iterative DFS for unbounded depth scenarios.

Traversal order can also vary by neighbor ordering. If deterministic output matters, define neighbor ordering explicitly.

Finally, developers sometimes stop after one traversal in disconnected graphs. To cover all nodes, loop through every node and start traversal for unvisited ones.

Summary

  • BFS and DFS are both fundamental O(V + E) traversal algorithms.
  • BFS is best for nearest-first exploration and shortest unweighted paths.
  • DFS is best for depth-oriented analysis and cycle-related logic.
  • Queue and stack behavior drive practical memory tradeoffs.
  • Correct visited handling and deterministic neighbor policy prevent many bugs.

Course illustration
Course illustration

All Rights Reserved.