Graph Traversal in Python: BFS vs DFS on an Adjacency List

A graph can be represented as an adjacency list: a dict mapping each node to the list of nodes it connects to. This post builds one with a disconnected component included on purpose, then walks it with a queue-based bfs and a recursive dfs from the same start nodes to compare the order each visits nodes in.

The code

from collections import deque


def bfs(graph, start):
    # Visit level by level using a queue (deque): explore all of start's
    # neighbors before any of their neighbors.
    visited = {start}
    order = []
    queue = deque([start])
    while queue:
        node = queue.popleft()
        order.append(node)
        for neighbor in graph[node]:
            if neighbor not in visited:
                visited.add(neighbor)
                queue.append(neighbor)
    return order


def dfs(graph, start):
    # Visit as deep as possible using a stack (or recursion): go down one
    # path fully before backtracking to try the next branch.
    visited = set()
    order = []

    def _visit(node):
        visited.add(node)
        order.append(node)
        for neighbor in graph[node]:
            if neighbor not in visited:
                _visit(neighbor)

    _visit(start)
    return order


graph = {
    "A": ["B", "C"],
    "B": ["A", "D", "E"],
    "C": ["A", "F"],
    "D": ["B"],
    "E": ["B", "F"],
    "F": ["C", "E"],
    "G": ["H"],
    "H": ["G"],
}

print("adjacency list:")
for node, neighbors in graph.items():
    print(f"  {node}: {neighbors}")

print("\nbfs(graph, 'A'):", bfs(graph, "A"))
print("dfs(graph, 'A'):", dfs(graph, "A"))

print("\nbfs(graph, 'G'):", bfs(graph, "G"))
print("dfs(graph, 'G'):", dfs(graph, "G"))

reached_from_a = set(bfs(graph, "A"))
print("\nis 'G' reachable from 'A'?", "G" in reached_from_a)
print("is 'F' reachable from 'A'?", "F" in reached_from_a)

bfs uses a deque as a FIFO queue, visiting all of a node’s neighbors before moving to the next level. dfs recurses into each unvisited neighbor immediately, going as deep as possible before backtracking. The graph has two components: A-B-C-D-E-F connected to each other, and G-H connected only to each other.

Running it

Real output:

adjacency list:
  A: ['B', 'C']
  B: ['A', 'D', 'E']
  C: ['A', 'F']
  D: ['B']
  E: ['B', 'F']
  F: ['C', 'E']
  G: ['H']
  H: ['G']

bfs(graph, 'A'): ['A', 'B', 'C', 'D', 'E', 'F']
dfs(graph, 'A'): ['A', 'B', 'D', 'E', 'F', 'C']

bfs(graph, 'G'): ['G', 'H']
dfs(graph, 'G'): ['G', 'H']

is 'G' reachable from 'A'? False
is 'F' reachable from 'A'? True

Starting from 'A', bfs visited ['A', 'B', 'C', 'D', 'E', 'F'] — both of A’s direct neighbors (B, C) before any of theirs. dfs visited ['A', 'B', 'D', 'E', 'F', 'C'] — it went A -> B -> D, backtracked, then B -> E -> F, and only reached C last. Both traversals stopped at 6 nodes, never touching G or H, since that pair forms a separate component. Starting from 'G' instead, both bfs and dfs only found ['G', 'H']. The reachability check confirmed it directly: 'G' was not in the set of nodes BFS reached from 'A', while 'F' was.

Takeaway

Same graph, two different visit orders from the same start node — BFS went neighbor-by-neighbor while DFS went deep down one branch first — and both correctly stayed within their connected component, never crossing into the separate G-H pair from A.