Expert: advanced patterns and interviews · Graphs · topic 42 of 61 · 40 min
Breadth-first search (BFS)
Explanation
The idea: ripples in water
Drop a stone in a pond and the ripples spread in rings: first the ring closest to the stone, then the next ring, and so on. Breadth-first search (BFS) explores a graph the same way. It visits the start, then every vertex one edge away, then every vertex two edges away. Nothing at distance 3 is visited before everything at distance 2.
The queue and the visited set
BFS keeps a queue: a line where items join at the back and leave from the front (first in, first out). In Python use collections.deque, whose popleft() is O(1). A plain list with pop(0) shifts every item, which is O(n) per pop.
Graphs can have cycles, so BFS also keeps a visited set: the vertices it has already put in the queue. Mark a vertex as visited WHEN YOU ADD IT to the queue, not when you take it out. Otherwise the same vertex can be added many times by different neighbours before it is processed.
from collections import deque
def bfs_dist(adj, start):
dist = {start: 0}
queue = deque([start])
while queue:
u = queue.popleft()
for v in adj[u]:
if v not in dist: # dist doubles as the visited set
dist[v] = dist[u] + 1
queue.append(v)
return dist
adj = {0: [1, 2], 1: [0, 3], 2: [0, 3], 3: [1, 2, 4], 4: [3]}
print(bfs_dist(adj, 0)) # Output: {0: 0, 1: 1, 2: 1, 3: 2, 4: 3}Every vertex enters the queue at most once and every edge is looked at once from each end, so BFS takes O(V + E) time with an adjacency list. The queue and the visited set take O(V) extra space.
Why BFS finds shortest paths
Because BFS works layer by layer, the first time it reaches a vertex is along a path with the fewest possible edges. So in an unweighted graph, dist[v] is the shortest distance from the start. "Fewest metro stops from Andheri to Churchgate" or "fewest connections between two people" are BFS questions. When edges have different weights (km, minutes, rupees), BFS is no longer enough; topic 5 covers Dijkstra.
Grids are graphs too
In a grid problem, each cell is a vertex and its neighbours are the cells up, down, left and right. You do not build an adjacency list; you compute neighbours on the fly with a list of four direction steps, checking that the new cell is inside the grid and not a wall. A grid with R rows and C columns has R × C vertices, so BFS costs O(R × C).
DIRS = [(1, 0), (-1, 0), (0, 1), (0, -1)]
for dr, dc in DIRS:
nr, nc = r + dr, c + dc
if 0 <= nr < rows and 0 <= nc < cols and grid[nr][nc] != "#":
... # (nr, nc) is a neighbour you may visitMulti-source BFS
Sometimes the spreading starts from many places at once. In the "rotting oranges" problem, every rotten orange makes its fresh neighbours rotten each minute. Put ALL the rotten oranges in the queue at minute 0, then run one BFS. Each layer is one minute. This is far faster than running a separate BFS from every source, and it answers "distance to the NEAREST source" for every cell.
To count layers, process the queue one layer at a time: for _ in range(len(queue)) pops exactly the vertices of the current layer, then you add 1 to the layer count.
Common mistakes
- Marking visited when a vertex is popped instead of when it is pushed. The answer may still be right, but vertices get queued many times. Fix: mark on push.
- Using list.pop(0) for the queue, which makes BFS O(V²) on big inputs. Fix: deque and popleft().
- Forgetting the case start == target, where the answer is 0. Fix: check it first, or let dist[start] = 0 answer it.
- Indexing grid[nr][nc] before checking 0 <= nr < rows. Negative indexes wrap around in Python, so the bug is silent. Fix: check bounds first.
- Mixing up "number of moves" and "number of cells on the path" (they differ by 1). Fix: read the prompt and test on a 1 × 1 grid.
- Using BFS on a graph with different edge weights. Fix: Dijkstra (topic 5).
Need help?
Stuck on a word or an idea? Search this course, or every course, and jump straight to the explanation or a worked question.
Try:
Watch the lesson
Breadth-first search (BFS) · 1/8
This lesson is about breadth-first search, the way to explore a graph in layers, like ripples in a pond.
Prefer a video by a teacher? Find free videos on “breadth first search bfs shortest path grid multi source explained python” on YouTube ↗
In real life: Nearest ambulance station for every block
An emergency-response team wants to know, for every block of a city map, how many blocks away the nearest ambulance station is. Running a BFS from every block would repeat a lot of work. Instead, put every station in the queue at distance 0 and run one multi-source BFS: each block is reached first from its nearest station. Walls (#) are blocks you cannot pass, such as a river or a closed road. Real dispatch systems use road networks and live traffic, but the layered idea is the same.
from collections import deque
city = ["S..#",
"..#.",
"...S"]
rows, cols = len(city), len(city[0])
dist = [[-1] * cols for _ in range(rows)]
queue = deque()
for r in range(rows):
for c in range(cols):
if city[r][c] == "S":
dist[r][c] = 0
queue.append((r, c))
while queue:
r, c = queue.popleft()
for dr, dc in ((1, 0), (-1, 0), (0, 1), (0, -1)):
nr, nc = r + dr, c + dc
if 0 <= nr < rows and 0 <= nc < cols and city[nr][nc] != "#" and dist[nr][nc] == -1:
dist[nr][nc] = dist[r][c] + 1
queue.append((nr, nc))
for row in dist:
print(row)Practice (0/4 done)
Write bfs_order(adj, start). adj is a dict mapping every vertex to the list of its neighbours (the graph may be directed and may have cycles). Return the list of vertices in the order BFS visits them from start, looking at neighbours in the order they are listed. Vertices that cannot be reached are not included. Example: bfs_order({0: [1, 2], 1: [3], 2: [3], 3: []}, 0) returns [0, 1, 2, 3].
Write shortest_path_len(n, edges, s, t) for an UNDIRECTED, unweighted graph with vertices 0 to n − 1 given as a list of pairs. Return the smallest number of edges on a path from s to t, 0 if s == t, and -1 if t cannot be reached. Use BFS. Example: shortest_path_len(5, [(0, 1), (1, 2), (2, 3), (3, 4), (0, 4)], 0, 3) returns 2.
Write grid_shortest_path(grid). grid is a list of equal-length strings: "." is open and "#" is a wall. Moving one step up, down, left or right costs 1 move (no diagonals). Return the fewest moves from the top-left cell to the bottom-right cell, or -1 if that is impossible, if either corner is a wall, or if the grid is empty. A 1 × 1 open grid needs 0 moves.
Write oranges_rotting(grid). grid is a list of lists of ints: 0 is an empty cell, 1 a fresh orange and 2 a rotten orange. Every minute, each rotten orange makes the fresh oranges directly up, down, left and right of it rotten. Return the number of minutes until no fresh orange is left, 0 if there are no fresh oranges at the start, and -1 if some fresh orange can never rot. Use multi-source BFS. Example: [[2, 1, 1], [1, 1, 0], [0, 1, 1]] returns 4.
Quick quiz
Read more (free)
Related help
Before this, learn
This topic builds on these. If something here feels new, open them first.
Related topics
- Depth-first search (DFS)Graphs · same module
- Topological sortGraphs · same module
- Minimum spanning treesTries, union-find and bit manipulation
- Core DSA patterns in C++C++ Programming (modern C++ and DSA) · Competitive programming, interviews and careers
- 2-D arrays (matrices)Arrays and strings
- 2-D DP on grids and stringsDynamic programming
Questions people ask about this
Open a question to see the answer and why. The first ones are this topic's own quiz, so try the quiz above before you peek.
Why should BFS mark a vertex as visited when it is added to the queue?
Answer: So the same vertex is not added to the queue many times
If you wait until it is popped, several neighbours can each add it before then.
Which is the right queue for BFS in Python?
Answer: collections.deque with popleft()
deque.popleft() is O(1); list.pop(0) shifts every item, O(n).
What is the time of BFS on a graph stored as an adjacency list?
Answer: O(V + E)
Each vertex is queued once and each edge is checked a constant number of times.
In "rotting oranges", how does the BFS start?
Answer: With every rotten orange in the queue at minute 0
Multi-source BFS: all sources share layer 0, so one pass gives each cell its distance to the nearest source.
In Python, a grid BFS computes nr = -1 and reads grid[nr][nc] without a bounds check. What happens?
Answer: It silently reads the last row
Negative indexes count from the end, so the bug hides. Check 0 <= nr < rows first.
Why can BFS give a wrong answer on a weighted graph?Related
Answer: It counts edges, not the total weight
BFS settles a vertex by the fewest edges, but a path with more, cheaper edges can cost less.
An undirected graph has 5 edges. What do all the degrees add up to?Related
Answer: 10
Every edge touches two vertices, so it adds 2 to the total: 2 × 5 = 10.
In word ladder, what are the vertices and the edges?Related
Answer: Words; two words that differ in exactly one letter
It is an implicit graph whose edges are generated by changing one letter.
Why use collections.deque instead of a list for BFS in Python?Related
Answer: popleft() is O(1), while list.pop(0) shifts every item, O(n)
Taking from the front of a list moves all other items; a deque is built for both ends.
Pass the quiz (60% or more) or mark this topic as done when you are ready.
Your progress is saved on this device only.
Next steps
- 4 more topics in “Graphs” before its exam: Depth-first search (DFS), Topological sort, Shortest paths: Dijkstra and friends, Graph problem patterns.
- Module 9 exam: Graphs · 14 questions · 25 min, without help. 6 topics of this module still to finish first.
- Then the next module, Tries, union-find and bit manipulation. 61 topics left in the course before the final exam.