Skip to content
Work Practice Products Coding interviews Machine learning coding Quant developer HFT course Blog Method Why me About Enquire Book a call

Coding · Graphs

Breadth-first traversal

Lesson · 10 min read · Graphs · Traversing a graph

Visit everything one step away, then two, then three: a queue gives the order, and the first visit to a node is along a shortest path.

In rings

Breadth-first search explores outwards from the start in rings: first every node one edge away, then every node two edges away, and so on. Where depth-first search commits to one branch, breadth-first search advances on every branch at once, one step at a time.

0d=01d=12d=13d=14d=25d=26d=27d=28d=1
Every node labelled with its distance from 0. Breadth-first search visits the d=1 ring before any node at d=2.

That order has a consequence worth more than the traversal itself: the first time the search reaches a node, it has done so by a path with the fewest possible edges. Breadth-first search is the shortest-path algorithm for unweighted graphs, and a whole pattern module later is built on it.

The algorithm

A first-in, first-out queue produces the rings automatically. Nodes at distance d are all in the queue before any node at distance d + 1 is added, because those are only added when a node at distance d is taken out.

Breadth-first traversal

bfs(s)

  1. Add s to visited and to the back of an empty queue.
  2. While the queue is not empty: take u from the front.
  3. For each neighbour v of u not in visited: add v to visited and to the back of the queue.

traverse(graph)

  1. For each node s: if s is not in visited, call bfs(s).

Mark when you enqueue

The one detail that separates a correct breadth-first search from a slow one is when a node is marked visited. Mark it when it joins the queue, not when it leaves.

sabt
t was queued from a. When b is processed, t is already marked, so it is not queued a second time.

If marking waited until a node was dequeued, t would be queued once from a and again from b. On a grid, where most cells have two or more neighbours already in the queue, that multiplies the work several times over and can turn an accepted solution into a timeout.

Step by step

The same seven-node graph as the depth-first lesson. Compare the orders: depth-first went 0, 1, 3, 2, 4; breadth-first goes 0, 1, 2, 3, 4, because 2 is one step from 0 while 3 is two.

Breadth-first traversal from node 0. The label d is the distance at which each node was found.

  1. Step 1 / 9
    0d=0123456
    queue (front → back)
    00
    visit order
    —0

    Mark 0 visited and put it in the queue.

  2. Step 2 / 9
    0d=01d=12d=13456
    queue (front → back)
    1021
    visit order
    00

    Take 0 from the front of the queue. Its unvisited neighbours 1, 2 are marked now and joined the back of the queue.

  3. Step 3 / 9
    0d=01d=12d=13d=2456
    queue (front → back)
    2031
    visit order
    0011

    Take 1 from the front of the queue. Its unvisited neighbours 3 are marked now and joined the back of the queue.

  4. Step 4 / 9
    0d=01d=12d=13d=2456
    queue (front → back)
    30
    visit order
    001122

    Take 2 from the front of the queue. It has no unvisited neighbours.

  5. Step 5 / 9
    0d=01d=12d=13d=24d=356
    queue (front → back)
    40
    visit order
    00112233

    Take 3 from the front of the queue. Its unvisited neighbours 4 are marked now and joined the back of the queue.

  6. Step 6 / 9
    0d=01d=12d=13d=24d=356
    queue (front → back)
    empty0
    visit order
    0011223344

    Take 4 from the front of the queue. It has no unvisited neighbours.

  7. Step 7 / 9
    0d=01d=12d=13d=24d=35d=06
    queue (front → back)
    50
    visit order
    0011223344

    The queue is empty but 5 is unvisited: start again from 5.

  8. Step 8 / 9
    0d=01d=12d=13d=24d=35d=06d=1
    queue (front → back)
    60
    visit order
    001122334455

    Take 5 from the front of the queue. Its unvisited neighbours 6 are marked now and joined the back of the queue.

  9. Step 9 / 9
    0d=01d=12d=13d=24d=35d=06d=1
    queue (front → back)
    empty0
    visit order
    00112233445566

    Take 6 from the front of the queue. It has no unvisited neighbours. The queue is empty and every node is visited. Order: 0, 1, 2, 3, 4, 5, 6.

just taken from the queuewaiting in the queueprocessededge that first reached a node

Implementation

Use a real queue: collections.deque in Python, ArrayDeque in Java, VecDeque in Rust. Removing from the front of a Python list or a JavaScript array costs O(n) and quietly makes the traversal quadratic; the JavaScript version below uses an array with a moving head index instead.

from collections import deque

def bfs_order(n, edges):
    adj = [[] for _ in range(n)]
    for u, v in edges:
        adj[u].append(v)
        adj[v].append(u)
    for nb in adj:
        nb.sort()
    seen = [False] * n
    order = []
    for s in range(n):
        if seen[s]:
            continue
        seen[s] = True
        q = deque([s])
        while q:
            u = q.popleft()
            order.append(u)
            for v in adj[u]:
                if not seen[v]:
                    seen[v] = True      # mark on enqueue, so v is queued once
                    q.append(v)
    return order

The same code is the reference solution to Breadth-first order of a graph, where it is compiled and run against the tests in all six languages.

Two variations come up constantly. To work level by level, take the queue's length at the start of each round and process exactly that many nodes: those are the current ring. To recover a shortest path, store for each node the node it was discovered from, then follow those links back from the target.

parent = {s: None}
q = deque([s])
while q:
    u = q.popleft()
    for v in adj[u]:
        if v not in parent:
            parent[v] = u          # parent doubles as the visited set
            q.append(v)
path = []
while t is not None:               # walk back from the target
    path.append(t)
    t = parent.get(t)
path.reverse()                     # only valid if the target was reached

Complexity, and which search to use

TimeO(n + m)
SpaceO(n)

Every node enters the queue once and every edge is examined once from each end. The queue can hold a whole ring at once: on a star, n − 1 nodes.

Depth-firstBreadth-first
Structurecall stack or explicit stackqueue
Orderone branch to the end, then back upring by ring from the start
Shortest path, unweightednoyes: the first visit is along a shortest path
Natural forcycles, components, topological order, enumerating pathsfewest steps, levels, nearest target
Extra memorydepth of the searchwidth of the widest ring

When either will do, as in counting components, pick the one you can write without a bug. When the question says "fewest", "minimum number of moves" or "nearest", it is breadth-first.