BFS and DFS

Breadth-First Search (BFS) and Depth-First Search (DFS) are the two fundamental ways to traverse graphs and trees. BFS explores level by level; DFS goes deep before backtracking.

Why It Matters

BFS finds shortest paths in unweighted graphs. DFS detects cycles, does topological sorting, and finds connected components. Nearly every graph problem starts with one of these.

Comparison

PropertyBFSDFS
Data structureQueue (FIFO)Stack / recursion (LIFO)
ExploresLevel by levelBranch by branch
Shortest pathYes (unweighted)No
MemoryO(width of graph)O(depth of graph)
Use forShortest path, level orderCycle detection, topological sort

BFS

from collections import deque
 
def bfs(graph: dict, start: str) -> list:
    visited = {start}
    queue = deque([start])
    order = []
    while queue:
        node = queue.popleft()
        order.append(node)
        for neighbor in graph.get(node, []):
            if neighbor not in visited:
                visited.add(neighbor)
                queue.append(neighbor)
    return order
 
# Shortest path (unweighted)
def shortest_path(graph: dict, start: str, end: str) -> list:
    queue = deque([(start, [start])])
    visited = {start}
    while queue:
        node, path = queue.popleft()
        if node == end:
            return path
        for neighbor in graph.get(node, []):
            if neighbor not in visited:
                visited.add(neighbor)
                queue.append((neighbor, path + [neighbor]))
    return []  # no path
 
graph = {'A': ['B', 'C'], 'B': ['D'], 'C': ['D'], 'D': ['E'], 'E': []}
assert shortest_path(graph, 'A', 'E') == ['A', 'B', 'D', 'E']

DFS

# Recursive DFS
def dfs_recursive(graph: dict, start: str, visited: set = None) -> list:
    if visited is None:
        visited = set()
    visited.add(start)
    order = [start]
    for neighbor in graph.get(start, []):
        if neighbor not in visited:
            order.extend(dfs_recursive(graph, neighbor, visited))
    return order
 
# Iterative DFS (using explicit stack)
def dfs_iterative(graph: dict, start: str) -> list:
    visited = set()
    stack = [start]
    order = []
    while stack:
        node = stack.pop()
        if node in visited:
            continue
        visited.add(node)
        order.append(node)
        for neighbor in reversed(graph.get(node, [])):
            if neighbor not in visited:
                stack.append(neighbor)
    return order
 
# Cycle detection in directed graph
def has_cycle(graph: dict) -> bool:
    WHITE, GRAY, BLACK = 0, 1, 2
    color = {node: WHITE for node in graph}
 
    def _dfs(node):
        color[node] = GRAY
        for neighbor in graph.get(node, []):
            if color.get(neighbor, WHITE) == GRAY:
                return True  # back edge = cycle
            if color.get(neighbor, WHITE) == WHITE and _dfs(neighbor):
                return True
        color[node] = BLACK
        return False
 
    return any(_dfs(node) for node in graph if color[node] == WHITE)

Tree Traversal with BFS/DFS

# Level-order traversal (BFS on a tree)
def level_order(root):
    if not root:
        return []
    result, queue = [], deque([root])
    while queue:
        level = []
        for _ in range(len(queue)):
            node = queue.popleft()
            level.append(node.val)
            if node.left:  queue.append(node.left)
            if node.right: queue.append(node.right)
        result.append(level)
    return result