Rotting Oranges: Multi-Source BFS on a Grid
"Rotting Oranges" is a Medium-level staple in Amazon, Google, Microsoft loops, and it is a textbook fit for the Tree BFS pattern — one of the nine patterns that cover the bulk of what FAANG coding interviews actually test. Part 9 of 11 in the Tree BFS arc.
The Problem
In a grid of empty cells (0), fresh oranges (1), and rotten oranges (2), rot spreads to 4-directionally adjacent fresh oranges every minute. Return the minutes until no fresh orange remains, or -1 if some can never rot. The go-to question for multi-source BFS.
Input: grid = [[2, 1, 1], [1, 1, 0], [0, 1, 1]]
Output: 4
Recognizing the Tree BFS Pattern
Breadth-first search explores a tree or graph level by level using a queue, and its defining superpower is the level snapshot: freeze the queue length, process exactly that many nodes, and you have processed one complete level. Anything phrased per-level — level lists, level averages, rightmost visible node, shortest path in unweighted structures — is BFS by construction.
Simultaneous spread from many origins in unit time steps is BFS with all sources enqueued at minute zero — the level structure then equals elapsed time. Running one BFS per source and combining is both wrong and slow; the single-queue seeding is the pattern.
The Approach
Scan once: enqueue every rotten cell, count fresh ones. Then level-snapshot BFS: each round rots the fresh neighbors of the current frontier, decrementing the fresh count, incrementing the clock per non-empty round. Finish: fresh count zero gives the clock; anything left gives -1.
Mark cells rotten at enqueue time, not at pop time — deferring the mark lets two frontier cells enqueue the same orange twice. That double-enqueue bug is the grid-BFS classic.
Python Solution
from collections import deque
def oranges_rotting(grid: list[list[int]]) -> int:
"""Minutes for rot to reach every fresh orange, else -1."""
rows, cols = len(grid), len(grid[0])
queue: deque[tuple[int, int]] = deque()
fresh = 0
for r in range(rows):
for c in range(cols):
if grid[r][c] == 2:
queue.append((r, c))
elif grid[r][c] == 1:
fresh += 1
minutes = 0
while queue and fresh:
minutes += 1
for _ in range(len(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 grid[nr][nc] == 1:
grid[nr][nc] = 2 # mark at enqueue time
fresh -= 1
queue.append((nr, nc))
return minutes if fresh == 0 else -1
Complexity
- Time: O(m·n) — each cell is enqueued at most once
- Space: O(m·n) — queue worst case; rot marks reuse the grid
Interview Tips and Follow-Ups
- The
while queue and freshguard prevents counting a final round that rots nothing — the standard off-by-one-minute bug. - Walls and Gates and 01 Matrix are the same multi-source template — distance-from-nearest-source problems, all of them.
- If mutation of the input grid is off-limits, say you would copy it or keep a visited set, and what that costs.
More Tree BFS problems — and the other eight patterns — live in the Technical Interview category. Drill the pattern, not the problem: that is the entire thesis of this series.
Keep reading
Average of Levels: Aggregating Inside the Snapshot
Replace the level list with a running sum — the aggregation variant. Python solution and complexity analysis for the BFS interview pattern.
Level Order Traversal Bottom Up: One Reverse Away
Bottom-up levels — resist cleverness, reverse at the end. Python solution and complexity analysis for the BFS interview pattern.
Binary Tree Level Order Traversal: The BFS Template
The level-snapshot queue loop that eight other questions reuse verbatim. Python solution and complexity analysis for the BFS interview pattern.
Newsletter
New posts, straight to your inbox
One email per post. No spam, no tracking pixels, unsubscribe anytime.
Comments
- No comments yet. Be the first.