Skip to content
Forge Learn/Graph Fundamentals

BFS & DFS

6 min read

You'll learn to

  • -Implement bfs(graph, start) using a queue, and explain why it finds shortest paths in unweighted graphs
  • -Implement dfs(graph, start) using recursion, and explain why it explores depth-first
  • -Trace both traversals over the same small graph and compare their visit orders

Given a graph and a starting node, there are two fundamentally different orders in which you can visit every reachable node: breadth-first (spread outward one "ring" of distance at a time) and depth-first (commit to one path and follow it as far as possible before backtracking). Both are complete - both eventually visit every reachable node - but the order they visit nodes in, and the guarantees that order gives you, are very different.

BFS: Level by Level

Breadth-first search uses a queue (FIFO - first in, first out) to guarantee that every node at distance k from the start is visited before any node at distance k + 1. The start node is enqueued first; each time a node is dequeued, its unvisited neighbors are marked visited and enqueued. Because the queue preserves discovery order, and every neighbor of a distance-k node is at distance k or k+1, the traversal naturally exhausts one full "ring" of distance before touching the next.

bfs(graph, start) - a queue enforces level-by-level order

DFS: Go Deep First

Depth-first search instead commits to a neighbor immediately, recursing (or pushing onto an explicit stack) into that neighbor's subtree before ever returning to try a sibling neighbor. That "go all the way down one path before backing up" behavior is what recursion naturally gives you for free - each recursive call is itself a full depth-first exploration of everything reachable from that node.

dfs(graph, start) - recursion naturally explores one branch to completion before the next

A Worked Trace

Take the small undirected square graph from the previous chapter: A connects to B and C; B connects to A and D; C connects to A and D; D connects to B and C. Starting BFS from A: A is dequeued first and its neighbors B, C are enqueued; B is dequeued next and its only unvisited neighbor D is enqueued; C is dequeued (D is already visited, nothing new to enqueue); finally D is dequeued. The visit order is A, B, C, D - both of A's direct neighbors visited before D, which is two hops away. Starting DFS from A instead: visiting A recurses immediately into its first neighbor B; from B, the first unvisited neighbor is D, so DFS recurses into D immediately (skipping past C for now); from D, the only unvisited neighbor is C, so DFS visits C last. The visit order is A, B, D, C - notice C, one hop from the start, is visited *after* D, two hops away, because DFS commits to a path instead of expanding outward evenly.

  • -BFS: queue-based, visits nodes in increasing order of distance from start, and is the standard way to find shortest paths in unweighted graphs.
  • -DFS: recursion/stack-based, dives down one path fully before backtracking - useful for cycle detection, connectivity checks, and exploring all paths.
  • -Both run in O(n + e) time and O(n) space, visiting every node and edge at most once - the difference is purely visit *order*, not asymptotic cost.

If a problem asks for the shortest path (fewest hops) in an unweighted graph, reach for BFS - DFS can reach the target via a much longer path first and has no built-in guarantee of finding the shortest one.

Check Yourself1 / 3

Why does BFS find the shortest path in an unweighted graph, while DFS does not?

Ready to Build This?

Level 73: Graph Representations & BFS/DFS asks you to implement bfs(graph, start) and dfs(graph, start) over an adjacency-list dict, exactly as built in this chapter.

ScaleDojo Logo
Initializing ScaleDojo