Breadth-First Search

Watch BFS explore a graph level by level, visiting all neighbors before going deeper.

Time O(V + E) · Space O(V)

Breadth First Search Operations

Node
Edge
→
Run
Start
Target
100%
Ctrl + scroll to zoom

Implementation

Python
from collections import deque
from typing import Dict, Set, List, Any


def bfs(graph: Dict[Any, Set[Any]], start: Any) -> List[Any]:
    """
    Perform BFS traversal starting from a given node.

    Args:
        graph: Adjacency list representation {node: {neighbors}}
        start: Starting node for traversal

    Returns:
        List of nodes in BFS order (level by level)

    Raises:
        ValueError: If start node is not in graph
    """
    if start not in graph:
        raise ValueError(f"Start node '{start}' not in graph")

    visited = {start}
    queue = deque([start])
    order = []

    while queue:
        node = queue.popleft()  # O(1) - why we use deque instead of list
        order.append(node)

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

    return order


def bfs_with_levels(graph: Dict[Any, Set[Any]], start: Any) -> Dict[Any, int]:
    """
    Perform BFS and return the level (distance) of each reachable node.

    Args:
        graph: Adjacency list representation
        start: Starting node

    Returns:
        Dictionary mapping each reachable node to its level (distance from start)
    """
    if start not in graph:
        raise ValueError(f"Start node '{start}' not in graph")

    levels = {start: 0}
    queue = deque([start])

    while queue:
        node = queue.popleft()

        for neighbor in graph[node]:
            if neighbor not in levels:
                levels[neighbor] = levels[node] + 1
                queue.append(neighbor)

    return levels


def bfs_shortest_path(graph: Dict[Any, Set[Any]], start: Any, target: Any) -> List[Any] | None:
    """
    Find the shortest path between two nodes using BFS.

    In an unweighted graph, BFS guarantees the shortest path
    (minimum number of edges) because it explores level by level.

    Args:
        graph: Adjacency list representation
        start: Starting node
        target: Target node to reach

    Returns:
        List of nodes representing the shortest path, or None if no path exists
    """
    if start not in graph:
        raise ValueError(f"Start node '{start}' not in graph")
    if target not in graph:
        raise ValueError(f"Target node '{target}' not in graph")

    if start == target:
        return [start]

    visited = {start}
    queue = deque([(start, [start])])  # (node, path_to_node)

    while queue:
        node, path = queue.popleft()

        for neighbor in graph[node]:
            if neighbor not in visited:
                new_path = path + [neighbor]

                if neighbor == target:
                    return new_path

                visited.add(neighbor)
                queue.append((neighbor, new_path))

    return None  # No path found


# Example usage and demonstration
if __name__ == "__main__":
    # Example graph (undirected, represented with bidirectional edges)
    #
    #       A
    #      / \
    #     B   C
    #    / \   \
    #   D   E   F
    #
    example_graph = {
        "A": {"B", "C"},
        "B": {"A", "D", "E"},
        "C": {"A", "F"},
        "D": {"B"},
        "E": {"B"},
        "F": {"C"},
    }

    print("BFS Traversal:", bfs(example_graph, "A"))
    print("Node Levels:", bfs_with_levels(example_graph, "A"))
    print("Shortest Path A->F:", bfs_shortest_path(example_graph, "A", "F"))
About Breadth-First Search

How Breadth-First Search Works

Breadth-first search (BFS) explores a graph by visiting all neighbors of a node before moving to the next level. It uses a queue to process nodes in the order they were discovered. BFS finds the shortest path in unweighted graphs.

Start at the source node, mark it as visited, and add it to a queue. While the queue is not empty, dequeue a node, process it, and enqueue all its unvisited neighbors. This guarantees level-by-level exploration.

Complexity

Time
O(V + E)
Space
O(V)

When to Use Breadth-First Search

  • Shortest path in unweighted graphs
  • Level-order tree traversal
  • Finding connected components and web crawling