Skip to content
Insight CodeAcademy

BFS vs DFS: Which Traversal Should You Use?

In this article we will learn how breadth-first search and depth-first search actually differ, why BFS finds the shortest path in an unweighted graph and DFS does not, how their memory usage compares on wide and deep graphs, and a decision rule for choosing between them in an interview.

Naveen Garg6 min read
BFS vs DFS: Which Traversal Should You Use?

BFS and DFS are the two ways to visit every node of a graph, and almost every graph algorithm is one of them with something extra bolted on. Choosing correctly between them is usually the first decision in a graph problem, and getting it wrong makes the rest of the solution much harder than it needs to be.

In this article we will learn how the two traversals actually differ, why BFS finds the shortest path in an unweighted graph and DFS does not, how their memory usage compares on wide versus deep graphs, and a decision rule you can apply in an interview.

Short answer

Breadth-first search visits nodes in order of their distance from the starting node: all nodes one edge away, then all nodes two edges away, and so on. It uses a queue. Because it explores in expanding rings, the first time it reaches a node is guaranteed to be by a shortest path in an unweighted graph.

Short answer

Depth-first search follows one path as far as it goes before backtracking to try the next branch. It uses a stack, usually the call stack via recursion. It reaches nodes quickly but with no guarantee about path length — the first time it reaches a node may be by a long, winding route.

How do they differ in practice?

Consider this graph, with A as the start:

Same graph, same start. The numbers show the order each traversal visits the nodes.

BFS visits: A, then B and C, then D, E and F, then G. Level by level.

DFS visits: A, B, D, G, backtrack, E, backtrack, C, F. One branch to the bottom, then the next.

Both reach G. BFS reaches it at distance 3 via A → B → D → G, and that is genuinely the shortest. DFS also happens to reach it via that route here, but only by luck of the ordering — reorder the adjacency lists and DFS could find a longer path first and accept it.

The implementations

from collections import deque
 
def bfs(graph, start):
    visited = {start}
    queue = deque([start])
    order = []
 
    while queue:
        node = queue.popleft()      # FIFO — this is what makes it breadth-first
        order.append(node)
        for neighbour in graph[node]:
            if neighbour not in visited:
                visited.add(neighbour)   # mark on enqueue, not on dequeue
                queue.append(neighbour)
 
    return order
def dfs(graph, start, visited=None, order=None):
    if visited is None:
        visited, order = set(), []
 
    visited.add(start)
    order.append(start)
 
    for neighbour in graph[start]:
        if neighbour not in visited:
            dfs(graph, neighbour, visited, order)
 
    return order

The only structural difference is the queue versus the stack. Swap popleft() for pop() in the BFS code and you have an iterative DFS.

What each traversal is holding as it runs. Colours match the levels in the graph above.

Mark nodes visited when you enqueue them in BFS, not when you dequeue them. Marking on dequeue lets the same node be added to the queue several times before it is first processed, which can blow up memory on dense graphs.

Which one should you use?

BFSDFS
Data structureQueue (FIFO)Stack / recursion (LIFO)
Finds shortest path (unweighted)Yes, guaranteedNo
Time complexityO(V + E)O(V + E)
Space complexityO(w) — widest levelO(d) — deepest path
Good on deep, narrow graphsYesRisk of stack overflow
Good on wide, shallow graphsQueue can get largeYes
Cycle detectionWorksMore natural
Topological sortKahn's algorithmNatural via post-order
Connected componentsWorksWorks, usually simpler
Finds a path fastSlowerOften faster
BFS memory follows the widest level; DFS memory follows the deepest path.

The decision rule

Ask one question: does the problem care about distance?

  • "Shortest", "minimum number of steps", "fewest moves", "level", "nearest" → BFS
  • "Does a path exist", "all paths", "connected components", "detect a cycle", "topological order" → DFS
One question decides most graph problems.

Two practical caveats. On a graph with weighted edges, neither is correct for shortest paths — you need Dijkstra. And if the graph might be very deep, recursive DFS can exhaust the call stack; convert it to an explicit stack.

Common mistakes

  • Using DFS for a shortest-path question. The most common graph error in interviews.
  • Marking visited at the wrong moment in BFS. See the callout above.
  • Forgetting the visited set entirely. On a cyclic graph, both traversals loop forever.
  • Recursing on a deep graph. Python defaults to a recursion limit around 1,000.
  • Assuming BFS works for weighted graphs. It gives fewest edges, not lowest cost.

Conclusion

BFS and DFS have identical O(V + E) time complexity and differ only in the order they visit nodes: BFS in expanding rings from the source using a queue, DFS one branch at a time using a stack.

That ordering difference is decisive. BFS guarantees shortest paths in unweighted graphs and uses memory proportional to the widest level. DFS gives no path-length guarantee, uses memory proportional to the deepest path, and is the more natural fit for cycle detection, topological sorting and exhaustive path enumeration.

If distance matters, use BFS. Otherwise DFS is usually simpler.

Frequently asked questions

Is BFS always better than DFS for finding a path?
Only if you need the shortest path in an unweighted graph, which BFS guarantees and DFS does not. If any path will do, DFS is often preferable because it usually uses less memory and is simpler to write recursively.
Can DFS find the shortest path?
Only by exploring every path exhaustively and keeping the best, which is far more expensive than BFS. For weighted graphs neither plain traversal is correct — you need Dijkstra for non-negative weights or Bellman-Ford when negative weights exist.

Read next

DSA7 min read

The Sliding Window Pattern

In this article we will learn what the sliding window pattern is, how it turns an O(n²) brute force into an O(n) scan, the difference between fixed-size and variable-size windows, how to recognise a sliding window problem from its wording, and the mistakes that quietly break the invariant.

DSA8 min read

Time and Space Complexity Explained

In this article we will learn what time and space complexity mean, why Big-O notation drops constants, how to analyse loops and recursion step by step, what auxiliary space is, and how to compare two solutions without ever running them.

System Design7 min read

SQL vs NoSQL: How to Actually Choose

In this article we will learn what genuinely separates SQL and NoSQL databases, why the common answers about scale and schema are mostly wrong, the four NoSQL families and what each is for, and a decision procedure you can defend in a system design interview.