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.
| Aspect | BFS | DFS |
|---|---|---|
| Data structure | Queue (FIFO) | Stack (LIFO) |
| Next node | Oldest unvisited neighbor | Most recently discovered neighbor |
| Pattern | Expands like a wave outward | Dives down one branch, then backtracks |
| Guarantees | Shortest path by edge count | Full path exploration |
| Classic uses | Shortest path, level-order, social graph distance | Cycle 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.
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
Level 1
Level 2
QUEUE - FIFO
Oldest discovered node leaves first.
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.
| Aspect | BFS | DFS |
|---|---|---|
| Data structure | Queue (FIFO) | Stack (LIFO) |
| Visits neighbors | Level by level | Branch by branch |
| Shortest path | Guaranteed in unweighted graphs | Not guaranteed |
| Memory | O(width) - wide graphs are costly | O(depth) - deep graphs are costly |
| Best for | Shortest path, level-order, social distance | Cycle detection, topological sort, maze solving |
| Worst case | Very wide graphs | Very 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.