Implementing BFS and DFS in Python: Hands-On Guide
Graph traversal is foundational to coding interviews and real-world systems. This hands-on guide teaches you implementing BFS and DFS in Python from scratch, with clear code, edge cases, and performance tips.
Table of Contents
Hands-On Guide to Implementing BFS and DFS in Python
Graphs are everywhere — from social networks and navigation apps to compiler design and web crawling. If you want to solve problems involving connections, shortest paths, or searching through data that isn’t neatly linear, you need graph traversal. And in Python, two algorithms stand above the rest: BFS (Breadth-First Search) and DFS (Depth-First Search).
In this guide, you’ll learn implementing BFS and DFS in Python step by step. We’ll cover both recursive and iterative approaches, compare their time and space complexity, and show you exactly when to use each one. By the end, you’ll be ready to apply these algorithms to coding challenges, real-world projects, and technical interviews.
If you’re also interested in other foundational algorithms, check out our guide on Python Binary Search: Implementation Guide with Examples to strengthen your core algorithmic skills.
Why Graph Traversal Matters
Before writing code, let’s understand the problem. A graph consists of nodes (vertices) and edges connecting them. Traversal means visiting every reachable node in a systematic way.
- BFS explores neighbors level by level (like ripples in a pond).
- DFS goes as deep as possible down one path before backtracking (like exploring a maze).
When you start implementing BFS and DFS in Python, you’re building a mental toolkit that applies to: - Web crawlers (DFS-style or BFS-style crawling)
- GPS shortest paths (BFS for unweighted graphs)
- Detecting cycles in dependency graphs
- Solving puzzles (mazes, word ladders)
- Social network friend recommendations
We’ve written a separate deep dive on Real-World Applications of Graph Traversal Algorithms — make sure to read that after mastering the code here.
Representing Graphs in Python
To start implementing BFS and DFS in Python, you first need a way to represent a graph. The two most common representations are:
1. Adjacency List (Most common)
graph = {
'A': ['B', 'C'],
'B': ['A', 'D', 'E'],
'C': ['A', 'F'],
'D': ['B'],
'E': ['B', 'F'],
'F': ['C', 'E']
}
2. Adjacency Matrix (Good for dense graphs)
# 6x6 matrix for vertices A-F
matrix = [
[0,1,1,0,0,0],
[1,0,1,1,0,0],
...
]
For most coding interviews and practical scenarios, the adjacency list is the go-to choice. It’s memory-efficient and fast to iterate over neighbors.
Throughout this guide, we’ll use the adjacency list shown above.
Implementing BFS in Python (Breadth-First Search)
BFS relies on a queue — first in, first out (FIFO). Python’s collections.deque is perfect for this.
Iterative BFS (Most Common)
Python
from collections import deque
def bfs(graph, start):
visited = set()
queue = deque([start])
visited.add(start)
order = [] # traversal order for demonstration
while queue:
vertex = queue.popleft()
order.append(vertex)
for neighbor in graph[vertex]:
if neighbor not in visited:
visited.add(neighbor)
queue.append(neighbor)
return order
# Example usage
graph = {
'A': ['B', 'C'],
'B': ['A', 'D', 'E'],
'C': ['A', 'F'],
'D': ['B'],
'E': ['B', 'F'],
'F': ['C', 'E']
}
print(bfs(graph, 'A')) # Output: ['A', 'B', 'C', 'D', 'E', 'F']
BFS to Find Shortest Path (Unweighted Graph)
One major advantage of BFS is that it finds the shortest number of edges between nodes.
Python
def bfs_shortest_path(graph, start, goal):
visited = set()
queue = deque([(start, [start])]) # (node, path_so_far)
while queue:
vertex, path = queue.popleft()
if vertex == goal:
return path
for neighbor in graph[vertex]:
if neighbor not in visited:
visited.add(neighbor)
queue.append((neighbor, path + [neighbor]))
return None # No path found
print(bfs_shortest_path(graph, 'A', 'F')) # ['A', 'C', 'F'] or ['A', 'B', 'E', 'F']
When you’re implementing BFS and DFS in Python, remember: BFS guarantees the shortest path in unweighted graphs. DFS does not.
Implementing DFS in Python (Depth-First Search)
DFS can be implemented recursively (elegant but risks recursion depth limits) or iteratively (using a stack).
Recursive DFS (Simplest)
Python
def dfs_recursive(graph, node, visited=None, order=None):
if visited is None:
visited = set()
if order is None:
order = []
visited.add(node)
order.append(node)
for neighbor in graph[node]:
if neighbor not in visited:
dfs_recursive(graph, neighbor, visited, order)
return order
print(dfs_recursive(graph, 'A')) # Output: ['A', 'B', 'D', 'E', 'F', 'C']
Iterative DFS (Safer for large graphs)
Python
def dfs_iterative(graph, start):
visited = set()
stack = [start]
order = []
while stack:
vertex = stack.pop()
if vertex not in visited:
visited.add(vertex)
order.append(vertex)
# Add neighbors in reverse order to simulate recursive order
for neighbor in reversed(graph[vertex]):
if neighbor not in visited:
stack.append(neighbor)
return order
print(dfs_iterative(graph, 'A')) # Output: ['A', 'B', 'D', 'E', 'F', 'C']
The iterative version avoids recursion depth limits and is often preferred in production code. Both approaches are valid when implementing BFS and DFS in Python — choose based on your constraints.
BFS vs DFS: Key Differences and When to Use Which
Architectural Comparison: BFS vs. DFS
| Feature | Breadth-First Search (BFS) | Depth-First Search (DFS) |
|---|---|---|
| Underlying Data Structure | Queue (First-In, First-Out / FIFO) | Stack (Last-In, First-Out / LIFO) or System Call Stack (via recursion) |
| Space Complexity | $O(V)$ worst-case
(proportional to the maximum width of the graph) | $O(V)$ worst-case for skewed graphs;
$O(\log V)$ for balanced trees (proportional to maximum depth) |
| Shortest Path Guarantee | Yes (guaranteed for unweighted graphs) | No (finds a valid path, not necessarily the shortest) |
| Completeness | Complete
(always finds a solution if one exists) | Conditional
(may hang in infinite paths/cycles if visited states are not tracked) |
| Primary Use Cases | Nearest-neighbor tracking, level-order traversals, peer-to-peer networks, routing algorithms. | Topological sorting, cycle detection, maze routing, constraint satisfaction puzzles. |
Key Architectural Trade-offs
Memory Footprint: BFS holds an entire layer of the graph in memory at once, making it memory-intensive for high-branching factor structures. DFS only holds the path from the root to the current node, maintaining a significantly lighter footprint in deep, narrow structures.
Search Optimality: BFS evaluates nodes objectively by distance from the origin, meaning it discovers the optimal path first. DFS commits blindly to a single path, meaning it may waste computation exploring a dead end millions of nodes deep when the target was adjacent to the starting node.
When to choose BFS:
- Finding shortest path in unweighted graphs (e.g., social distance)
- Web crawling level-by-level
- GPS navigation with equal edge weights
When to choose DFS: - Detecting cycles in a graph
- Topological sorting (prerequisite graphs)
- Solving mazes or puzzles where you want any path
- Memory-constrained environments (iterative DFS can be more memory efficient than BFS in sparse graphs)
Understanding these tradeoffs is critical when implementing BFS and DFS in Python for real problems. For a deeper look at performance, see our guide on Mastering Time Complexity in Python: A Complete Guide.
Advanced BFS and DFS Patterns
1. BFS on a Grid (Matrix)
Graphs aren’t always explicit. A 2D grid is a graph where each cell connects to its neighbors.
Python
from collections import deque
def bfs_grid(grid, start, target):
rows, cols = len(grid), len(grid[0])
directions = [(0,1), (1,0), (0,-1), (-1,0)]
visited = set()
queue = deque([(start[0], start[1], 0)]) # (r, c, distance)
while queue:
r, c, dist = queue.popleft()
if (r, c) == target:
return dist
for dr, dc in directions:
nr, nc = r + dr, c + dc
if 0 <= nr < rows and 0 <= nc < cols and (nr, nc) not in visited:
visited.add((nr, nc))
queue.append((nr, nc, dist + 1))
return -1
grid = [[0,0,0],[0,1,0],[0,0,0]]
print(bfs_grid(grid, (0,0), (2,2))) # Manhattan distance ignoring obstacles
2. DFS for Cycle Detection in Directed Graph
Python
def has_cycle_directed(graph):
visited = set()
recursion_stack = set()
def dfs(node):
visited.add(node)
recursion_stack.add(node)
for neighbor in graph[node]:
if neighbor not in visited:
if dfs(neighbor):
return True
elif neighbor in recursion_stack:
return True
recursion_stack.remove(node)
return False
for node in graph:
if node not in visited:
if dfs(node):
return True
return False
3. DFS for Connected Components
Python
def connected_components(graph):
visited = set()
components = []
def dfs(start, component):
stack = [start]
while stack:
node = stack.pop()
if node not in visited:
visited.add(node)
component.append(node)
for neighbor in graph[node]:
if neighbor not in visited:
stack.append(neighbor)
for node in graph:
if node not in visited:
comp = []
dfs(node, comp)
components.append(comp)
return components
print(connected_components(graph))
These patterns appear constantly in coding challenges. If you’re preparing for interviews, also check out Top Graph Algorithm Interview Questions & Answers.
Common Mistakes When Implementing BFS and DFS in Python
Even experienced developers make errors. Here’s what to watch for:
1. Forgetting to Mark Visited Before Enqueue (BFS)
Python
# WRONG
if neighbor not in visited:
queue.append(neighbor) # ← visited added too late
visited.add(neighbor) # can cause duplicates in queue
# RIGHT
if neighbor not in visited:
visited.add(neighbor)
queue.append(neighbor)
2. Using List as a Queue (BFS)
Python
# WRONG (O(n) pop(0))
queue = []
queue.append(x)
item = queue.pop(0) # SLOW!
# RIGHT
from collections import deque
queue = deque([start])
item = queue.popleft()
3. Recursion Depth Limit (DFS)
Python’s default recursion limit is ~1000. For large graphs, use iterative DFS or increase limit:
Python
import sys
sys.setrecursionlimit(1000000)
4. Modifying Graph While Iterating
Python
# WRONG
for neighbor in graph[node]:
graph[node].append(new_node) # changes size during iteration
# RIGHT - create a copy or use a separate structure
These pitfalls are similar to those in other algorithms. Read Common Mistakes in Binary Search Implementation to see how traversal errors compare.
Performance Analysis for BFS and DFS in Python
Time Complexity
Both BFS and DFS visit each vertex and edge once:
- O(V + E) for adjacency list
- O(V²) for adjacency matrix
Space Complexity
- BFS: O(V) for queue + visited set. In worst case (dense graph), queue holds most vertices.
- DFS iterative: O(V) for stack + visited
- DFS recursive: O(V) call stack depth (could overflow)
When implementing BFS and DFS in Python, always consider the graph density. BFS can use significantly more memory if the graph is wide (many neighbors per node).
For a deeper understanding of analyzing algorithm efficiency, explore Mastering Time Complexity Analysis for Data Structures and How to Calculate Big O Notation for Beginners.
Real-World Practice Problems
Try these challenges to solidify your skills:
- Word Ladder (LeetCode 127) – BFS on a graph where words are nodes and edges connect words differing by one letter.
- Number of Islands (LeetCode 200) – DFS or BFS on a grid to count connected components.
- Course Schedule (LeetCode 207) – DFS cycle detection in a directed graph.
- Pacific Atlantic Water Flow (LeetCode 417) – Multi-source BFS.
- Clone Graph (LeetCode 133) – BFS/DFS with a hashmap.
Implementing BFS and DFS in Python for these problems will prepare you for technical screens. Pair your practice with our Master Algorithm Implementation for Coding Interviews | Guide.
Debugging Graph Traversal Code
Even correct algorithms can fail due to subtle bugs. Here are debugging strategies:
- Print the visited set after each step to see the traversal order.
- Use small graphs (3–5 nodes) and trace manually.
- Check for disconnected nodes – your traversal should handle graphs with multiple components.
- Test both directed and undirected graphs separately.
For systematic debugging techniques, read Debugging Common Algorithm Mistakes: A Step-by-Step Guide and Debugging Python Code Like a Pro: Tips & Tricks.
Next Steps After Implementing BFS and DFS in Python
You’ve learned the core traversal algorithms. Now extend your knowledge:
- Weighted graphs → Dijkstra’s algorithm (BFS with priority queue)
- Negative weights → Bellman-Ford
- All-pairs shortest paths → Floyd-Warshall
- Minimum spanning trees → Prim’s (BFS-like) or Kruskal’s (DFS-like)
Understanding BFS and DFS makes advanced graph algorithms much easier to learn. Continue with our Data Structures in Python Coding: Complete Guide and Algorithmic Thinking in Python Coding.
Frequently Asked Questions
1. Can I use BFS and DFS on directed graphs exactly the same way?
Yes, both algorithms work unchanged on directed graphs. The only difference is that you only traverse edges in their given direction. The adjacency list representation remains the same.
2. Which is faster, BFS or DFS?
Neither is universally faster. Both are O(V+E). BFS can be slower in practice due to queue overhead, but it finds shortest paths. DFS may be faster if the solution is deep and the graph is narrow. Choose based on the problem, not raw speed.
3. How do I handle very large graphs when implementing BFS and DFS in Python?
Use iterative versions to avoid recursion limits. Consider using sys.setrecursionlimit() if recursion is necessary. For massive graphs that don’t fit in memory, use disk-based traversal or graph databases like Neo4j.
4. What’s the difference between DFS and backtracking?
DFS is a complete graph traversal algorithm. Backtracking is a problem-solving technique that uses DFS implicitly but prunes branches that can’t lead to a solution. Backtracking often modifies state as it goes (e.g., N-Queens).
5. Do I need to implement both BFS and DFS from scratch in interviews?
Yes, many interviews require whiteboarding one of them. Master both iterative and recursive versions. Also know when to use one over the other. Practice explaining the tradeoffs clearly.
Conclusion
You’ve just completed a comprehensive guide to implementing BFS and DFS in Python. You now understand:
- How to represent graphs using adjacency lists
- BFS with deque for shortest paths and level-order traversal
- DFS both recursively and iteratively using a stack
- Key differences, complexity analysis, and common pitfalls
- Real-world patterns like grid BFS and cycle detection
Graph traversal is a gateway to solving complex relationship problems. Whether you’re preparing for FAANG interviews, building a recommendation engine, or exploring social network analysis, BFS and DFS will be your reliable tools.
Keep practicing with coding challenges, and explore our related resources:
- Top Python Learning Resources for Students (2026 Guide)
- Python Project Ideas for Students & Beginners
- Career Development in Python Programming: Complete Guide
- Solving Overlapping Subproblems with Dynamic Programming
Now open your editor, create a graph, and start traversing. The connections are waiting.
Tags:
#bfs-and-dfs-in-python #bfs-dfs-implementation #coding-interview-prep**Canonical-URL:**-[https:// #graph-algorithms-in-python #graph-algorithms-python #python-graph-traversalRelated Posts
Binary Search Explained: Algorithm, Examples, & Edge Cases
Master the binary search algorithm with clear, step-by-step examples. Learn how to implement efficient searches in sorted arrays, avoid common …
Mar 11, 2026How to Approach Hard LeetCode Problems | A Strategic Framework
Master the mental framework and strategies to confidently break down and solve even the most challenging LeetCode problems.
Mar 06, 2026Two Pointer Technique | Master Array Problems in 8 Steps
Master the two-pointer technique to solve complex array and string problems efficiently. This guide breaks down patterns, provides step-by-step examples, …
Mar 11, 2026Need Coding Help?
Get expert assistance with your programming assignments and projects.