Quick Answer

Breadth-first search explores level by level using a queue, visiting all neighbours before going deeper, which is why it finds the shortest path in an unweighted graph. Depth-first search follows one branch as far as possible before backtracking, using a stack or recursion, which suits cycle detection, topological sorting and exhaustive path exploration. They differ only in the container used, but that difference changes both the guarantees and the memory profile.

The Only Real Difference

Both algorithms visit every reachable node exactly once, both track what they have seen, and both are O(V + E) — vertices plus edges. Written side by side, they differ in one line.

# BFS — a queue, so the OLDEST discovered node is explored next
from collections import deque

def bfs(graph, start):
    visited = {start}
    frontier = deque([start])
    while frontier:
        node = frontier.popleft()          # <- FIFO
        for nb in graph[node]:
            if nb not in visited:
                visited.add(nb)
                frontier.append(nb)

# DFS — a stack, so the NEWEST discovered node is explored next
def dfs(graph, start):
    visited = set()
    frontier = [start]
    while frontier:
        node = frontier.pop()              # <- LIFO
        if node in visited:
            continue
        visited.add(node)
        for nb in graph[node]:
            if nb not in visited:
                frontier.append(nb)

Change popleft() to pop() and breadth-first becomes depth-first. Everything else follows from that.

BFS spreads outward in rings: all nodes one step away, then all two steps away. DFS plunges down one path until it dead-ends, then backs up to the last unexplored branch.

Why Only BFS Finds the Shortest Path

This is the property that decides most real choices, and the reasoning is worth knowing rather than memorising.

BFS visits nodes in order of distance from the start. Everything at distance 1 is visited before anything at distance 2, because the queue holds them in discovery order. So the first time BFS reaches a node, it has arrived by a shortest route — no later path can be shorter, since all shorter distances were already exhausted.

DFS gives no such guarantee. It follows one branch to the end, so it might reach the target after a long wander when a two-step route existed. It finds a path, not the shortest one.

The critical caveat: this only holds for unweighted graphs, where every edge counts as one step. Once edges have different costs, the fewest-edges path may not be the cheapest, and BFS no longer answers the question. That is what Dijkstra's algorithm is for — effectively BFS with a priority queue so the cheapest frontier node is expanded first.

So: unweighted shortest path, use BFS. Weighted, use Dijkstra. Weighted with negative edges, use Bellman-Ford.

Memory Depends on the Shape of the Graph

Both are O(V + E) in time, but their space profiles are opposites, and which one is worse depends entirely on the graph.

BFS memory scales with the width of the graph, because the queue holds an entire level at once. On a wide, shallow structure — a social network where each person has hundreds of connections — that frontier explodes. A binary tree of depth 20 has about a million nodes in its last level, and BFS holds all of them.

DFS memory scales with the depth, because the stack holds only the current path. On the same wide tree, DFS holds just 20 nodes. But on a long chain of a million nodes, DFS holds all million — and recursive DFS will overflow the call stack long before that.

The practical rule: deep and narrow favours BFS; wide and shallow favours DFS. If you expect deep recursion in Python, write DFS iteratively with an explicit stack, since the default recursion limit is around 1000 and raising it is a workaround rather than a fix.

What Each Is Actually Used For

Reach for BFS when:

  • You need the shortest path or minimum number of steps in an unweighted graph — maze solving, word ladders, minimum moves on a board.
  • You want nodes grouped by distance, such as friends-of-friends within two hops.
  • You are searching for something likely to be near the start, since BFS checks close nodes first.
  • You are doing a level-order traversal of a tree, printing it row by row.

Reach for DFS when:

  • You need to detect a cycle. DFS naturally exposes back-edges to nodes still on the current path.
  • You need a topological sort — task scheduling, build order, course prerequisites. Push nodes as their exploration finishes and reverse the result.
  • You must explore every path or backtrack, as in sudoku solvers, N-queens and maze generation.
  • You are working with connected components or flood fill, where any traversal order works and DFS is simpler to write recursively.

For counting islands in a grid, either works — the question only asks how many components exist, not the route through them.

Mistakes That Cost Marks

Marking visited at the wrong moment in BFS. Mark a node when you enqueue it, not when you dequeue it. If you wait, the same node can be added several times by different neighbours before it is processed, and the queue balloons. The algorithm still terminates, but it is no longer O(V + E).

Using a list as a queue. list.pop(0) in Python and Array.shift() in JavaScript are O(n), because everything shifts down. Inside BFS that silently turns O(V + E) into something quadratic. Use collections.deque or Java's ArrayDeque.

Forgetting the visited set entirely. On a graph with cycles this loops forever. On a tree it happens to work, which is exactly why the bug survives testing and then hangs on the real input.

Assuming DFS gives shortest paths. It does not, and this is the most commonly penalised error on this topic.

Recursive DFS on deep graphs. Python's default recursion limit is about 1000 frames. A path longer than that raises RecursionError. Convert to an explicit stack rather than raising the limit.

Frequently Asked Questions

Is BFS or DFS faster? Neither. Both are O(V + E), since each vertex and edge is examined once. They differ in the order of exploration and in memory use, not in asymptotic speed. Which finishes sooner on a specific input depends on where the target happens to be.
Can DFS find the shortest path? Not reliably. DFS follows one branch to its end, so it may reach the target by a long route while a shorter one exists. For unweighted shortest paths use BFS; for weighted graphs use Dijkstra's algorithm.
Which uses more memory, BFS or DFS? It depends on the graph shape. BFS holds a whole level in its queue, so wide graphs cost more. DFS holds only the current path, so deep graphs cost more. Deep and narrow favours BFS; wide and shallow favours DFS.
Is recursion the same as DFS? Recursive traversal is DFS, because the call stack provides the last-in-first-out behaviour. You can also write DFS iteratively with an explicit stack, which is the safer choice when the graph might be deeper than the language's recursion limit.
When should I use Dijkstra instead of BFS? As soon as edges have different weights. BFS finds the path with the fewest edges, which is only the cheapest path when every edge costs the same. Dijkstra expands the cheapest frontier node first using a priority queue. For negative weights, use Bellman-Ford.