BFS vs DFS

Two ways to explore a graph - one expands outward level by level, the other dives deep before backtracking. The only difference is the data structure driving them.

You need the shortest path in an unweighted graph, or you need to process nodes level by level.

You need to explore all paths, detect cycles, check connectivity, or solve maze and backtracking problems.

Queue versus stack is the entire behavioral difference

BFS and DFS do not disagree about the graph. They disagree about which frontier node gets processed next. The queue makes BFS stay shallow. The stack makes DFS go deep.

AspectBFSDFS
Data structureQueue (FIFO)Stack (LIFO)
Next nodeOldest unvisited neighborMost recently discovered neighbor
PatternExpands like a wave outwardDives down one branch, then backtracks
GuaranteesShortest path by edge countFull path exploration
Classic usesShortest path, level-order, social graph distanceCycle detection, topological sort, connected components

Swap the queue for a stack and BFS becomes DFS. That is the entire difference.

See the data structure force the traversal order

The simulator keeps the graph fixed and changes only the frontier structure. Queue chips on the right explain BFS. Stack chips on the right explain DFS. The side-by-side tab makes the divergence explicit.

O(V+E) timeO(V) space
LeetCode #1971 - Find if Path ExistsStart: ATarget: F

Inside the demo

Use BFS when you need the shortest path in an unweighted graph or you need to process the graph level by level.

BFS levels

Level 0

A

Level 1

BC

Level 2

DEF
ACURRENTBCDEF

QUEUE - FIFO

Oldest discovered node leaves first.

A
Step 1 of 7

What

A added to queue. A visited. Queue: [A].

Why

BFS always starts by enqueuing the source. The queue ensures we process nodes in the order we discovered them - nearest first.

Code For This Pattern

function bfs(graph, start) {
  const visited = new Set();
  const queue = [start];         // FIFO - process in discovery order
  visited.add(start);

  while (queue.length > 0) {
    const node = queue.shift();  // dequeue oldest
    console.log(node);

    for (const neighbor of graph[node]) {
      if (!visited.has(neighbor)) {
        visited.add(neighbor);
        queue.push(neighbor);    // enqueue - will process after current level
      }
    }
  }
}

Same graph, same start node, different traversal priorities

BFS pays memory to keep an entire frontier. DFS pays memory to keep a deep path. Their best-use cases come directly from that tradeoff.

AspectBFSDFS
Data structureQueue (FIFO)Stack (LIFO)
Visits neighborsLevel by levelBranch by branch
Shortest pathGuaranteed in unweighted graphsNot guaranteed
MemoryO(width) - wide graphs are costlyO(depth) - deep graphs are costly
Best forShortest path, level-order, social distanceCycle detection, topological sort, maze solving
Worst caseVery wide graphsVery deep graphs (stack overflow risk)

The short version

  • BFS and DFS visit the same nodes - the order is what changes. That order is entirely determined by queue vs stack.
  • BFS processes all nodes at distance 1 before distance 2. That is why it guarantees the shortest path in unweighted graphs.
  • DFS dives as deep as possible before backtracking. That is why it is natural for exhaustive search, cycle detection, and path problems.
  • Swap queue.shift() for stack.pop() and BFS becomes DFS. The rest of the code is nearly identical.
  • BFS is expensive on wide graphs. DFS is expensive on deep graphs.