Graphs — Visual Learning

BFS Visualization —
A queue explores one layer at a time.

Breadth-First Search visits every neighbor of the current layer before moving to the next — using a plain FIFO queue, no recursion. It's the algorithm behind "shortest path in an unweighted graph" and "degrees of separation." Watch the queue fill and drain on LearnBug and see exactly why BFS finds the shortest path first.

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

What is BFS?

Starting from a node, visit all of its direct neighbors first, then all of their unvisited neighbors, and so on — layer by layer. A queue enforces this order: nodes are processed in the exact order they were discovered. A visited set stops nodes from being enqueued twice.

Why visualization helps

The confusing part isn't the queue mechanics — it's the visited set. Forget to mark a node visited when you enqueue it (instead of when you dequeue it), and the same node can be queued multiple times. LearnBug shows the queue and the visited set side by side at every step, so that bug becomes visible instead of silent.

YouTube — BFS Explained Visually
📺 Drop your YouTube embed here
LearnBug — queue draining level by level
🖼 Add a LearnBug screenshot here
When to Reach for BFS

BFS's signature use cases

Shortest path — unweighted

The first time BFS reaches a node, it's guaranteed to be via the shortest possible path (fewest edges) from the start.

Fewest edges

Level-by-level processing

Anything that needs to know "how many steps away" — social network degrees of separation, minimum moves in a puzzle, word ladder.

Min steps

Grid flood fill

Expanding outward from a cell — connected components, number of islands, rotting oranges — is BFS applied to a grid graph.

Flood fill
Walkthrough

BFS from A on A→[B,C], B→[D], C→[D]

1

Start: enqueue A, mark it visited immediately

Marking a node visited at enqueue time — not at dequeue time — is what prevents it from being added to the queue twice by two different neighbors later.

Astart
B
C
D
Queue: [A]  |  Visited: [A]
2

Dequeue A, visit it, enqueue its unvisited neighbors B and C

A's neighbors are B and C. Neither is visited yet, so both get marked visited and pushed onto the queue.

A
Bqueued
Cqueued
D
Queue: [B, C]  |  Visited: [A, B, C]  |  Output: [A]
3

Dequeue B, visit it, enqueue D — its only unvisited neighbor

B's only neighbor is D. D isn't visited yet, so it's marked visited and enqueued. Notice B was reached via the shortest path from A — one edge.

A
B
C
Dqueued
Queue: [C, D]  |  Visited: [A, B, C, D]  |  Output: [A, B]
4

Dequeue C — D is already visited, so it's skipped; then dequeue D

C also points to D, but D is already in the visited set, so it's not re-added to the queue. This is exactly what the visited-at-enqueue-time rule prevents: a duplicate D entry.

Return: [A, B, C, D] ✓ each node visited once, in shortest-path order from A

Python Code

BFS with a queue and visited set

PythonTraversal order — O(V+E) time, O(V) space
graph = {
    "A": ["B", "C"],
    "B": ["D"],
    "C": ["D"],
    "D": [],
}

def bfs(start):
    queue = [start]
    visited = {start}   # mark visited when ENQUEUED, not dequeued
    order = []
    while queue:
        node = queue.pop(0)
        order.append(node)
        for neighbor in graph[node]:
            if neighbor not in visited:
                visited.add(neighbor)
                queue.append(neighbor)
    return order
PythonShortest path — track distance alongside each node
def bfs_shortest_path(start, target):
    queue = [(start, 0)]
    visited = {start}
    while queue:
        node, dist = queue.pop(0)
        if node == target:
            return dist     # first time we reach target = shortest path
        for neighbor in graph[node]:
            if neighbor not in visited:
                visited.add(neighbor)
                queue.append((neighbor, dist + 1))
    return -1              # target unreachable

Complexity Notes

TimeO(V+E)Every vertex dequeued once, every edge examined once
SpaceO(V)Queue and visited set can each hold up to all vertices
Shortest path guaranteeUnweighted onlyBFS finds fewest-edges paths — for weighted graphs use Dijkstra instead
Mark-visited timingAt enqueueMarking at dequeue instead can enqueue the same node multiple times

Frequently asked questions

Why must I mark a node visited when I enqueue it, not when I dequeue it?

If two different nodes both point to the same unvisited neighbor before that neighbor is dequeued, marking visited only at dequeue time lets both of them enqueue it — the same node ends up in the queue twice, wasting work and potentially breaking distance calculations.

Why does BFS guarantee the shortest path in an unweighted graph?

BFS explores nodes in strict order of distance from the start — it fully processes everything at distance 1 before touching anything at distance 2. So the first time any node is reached, it must be via the shortest possible number of edges; there's no shorter path left unexplored.

How is BFS different from DFS?

BFS uses a queue (FIFO) and explores level by level; DFS uses a stack or recursion and dives as deep as possible before backtracking. Both are O(V+E), but BFS is the right tool for shortest-path/fewest-steps problems, while DFS suits path existence, cycle detection, and exhaustive exploration.

Does BFS work on directed graphs too?

Yes — BFS only follows the edges that actually exist in the adjacency list, so on a directed graph it simply won't traverse "backward" along an edge unless a reverse edge is explicitly present. The algorithm itself is unchanged.

Watch the queue explore one layer at a time

Paste your BFS into LearnBug and see the queue and visited set update at every step.

Open Playground →