- "Minimum minutes until every fresh orange rots" is a shortest-distance question on an unweighted grid, so use BFS.
- Put all rotten oranges in the queue at minute 0. That is multi-source BFS, equivalent to one super-source connected to every rotten orange.
- Process the queue level by level; each level is one minute. Rot fresh neighbours, decrement a
freshcounter. - Answer: the number of levels that rotted something. If
fresh > 0at the end, return-1. If there were no fresh oranges, return0. - O(m·n) time and space. The same pattern solves Walls and Gates, 01 Matrix and As Far from Land as Possible.
Rotting Oranges is the standard introduction to multi-source BFS, one of the most reusable ideas in grid and graph interviews. The problem itself is medium difficulty, but candidates lose points in predictable places: running a separate BFS from each rotten orange, using DFS, or getting the minute count off by one. This guide explains why a single BFS from all sources is correct, walks through a grid minute by minute, and then shows the five or six problems that are the same idea in disguise.
The Problem: How Long Until Every Orange Rots?
You get an m x n grid. Each cell is:
0: empty1: fresh orange2: rotten orange
Every minute, any fresh orange that is 4-directionally adjacent (up, down, left, right) to a rotten orange becomes rotten. Return the minimum number of minutes until no fresh orange remains, or -1 if that is impossible. Constraints are small on LeetCode (1 <= m, n <= 10), but interviewers will ask about large grids, so design for O(m·n).
Why BFS, and Why DFS Gives Wrong Answers
Rot spreads one step per minute in every direction at once. So the minute at which a fresh orange rots equals its grid distance to the nearest rotten orange, measured in steps through cells that contain oranges (rot cannot travel through empty cells). The answer to the problem is the largest such distance over all fresh oranges.
Shortest distances in an unweighted graph are exactly what BFS computes. BFS dequeues cells in non-decreasing order of distance from the source, so the first time it reaches a cell, that is the shortest distance to it, and it never needs to revisit.
DFS does not have that guarantee. It might reach an orange through a long winding path first and record distance 7, when a short path of length 2 exists via a different branch. You would have to allow revisits whenever a shorter distance is found, which turns into an exponential or at best O((m·n)2) relaxation. In an interview, choosing DFS here is a strong negative signal, because "minimum steps on an unweighted grid" should trigger BFS immediately. The general rules are summarised in graph BFS and DFS interview problems.
The simulation approach, and why it is slow
A naive solution simulates minute by minute: scan the whole grid, find fresh oranges next to rotten ones, rot them (careful to use a copy so newly rotten oranges do not spread in the same minute), repeat until nothing changes. Each scan is O(m·n), and the number of minutes can be up to O(m·n) (think of a long snake-shaped path of oranges), so this is O((m·n)2). It passes LeetCode's 10 × 10 limits but is worth mentioning only as the brute force.
The Multi-Source BFS Idea
There can be several rotten oranges at the start. You could run a BFS from each of them and take, for each fresh orange, the minimum distance across all runs. With k rotten oranges that is O(k·m·n), up to O((m·n)2) again.
The better idea: put all rotten oranges in the queue at the start, each at distance 0, and run a single BFS. This works because of a simple construction:
Imagine an extra node S connected to every rotten orange. A normal BFS from S would visit S, then all rotten oranges (distance "0" after subtracting the zero-length first hop), then their neighbours, and so on. Starting the queue with all rotten oranges is just skipping the first step of that BFS. Since a single-source BFS correctly computes shortest distances from S, our queue computes, for every cell, the distance to its nearest rotten orange. Each cell enters the queue once: O(m·n) total.
Rotting Oranges Code in Python, Java and C++
Plan: one pass to enqueue every rotten orange and count fresh ones. Then BFS level by level. For each level, if anything rots, that level counts as a minute. Mark oranges rotten in the grid itself when you enqueue them, which doubles as the visited set.
from collections import deque
from typing import List
class Solution:
def orangesRotting(self, grid: List[List[int]]) -> int:
rows, cols = len(grid), len(grid[0])
q = deque()
fresh = 0
for r in range(rows):
for c in range(cols):
if grid[r][c] == 2:
q.append((r, c))
elif grid[r][c] == 1:
fresh += 1
minutes = 0
dirs = ((1, 0), (-1, 0), (0, 1), (0, -1))
while q and fresh > 0:
for _ in range(len(q)): # one full level = one minute
r, c = q.popleft()
for dr, dc in dirs:
nr, nc = r + dr, c + dc
if 0 <= nr < rows and 0 <= nc < cols and grid[nr][nc] == 1:
grid[nr][nc] = 2 # mark when ENQUEUING
fresh -= 1
q.append((nr, nc))
minutes += 1
return minutes if fresh == 0 else -1
import java.util.ArrayDeque;
import java.util.Deque;
class Solution {
public int orangesRotting(int[][] grid) {
int rows = grid.length, cols = grid[0].length;
Deque<int[]> q = new ArrayDeque<>();
int fresh = 0;
for (int r = 0; r < rows; r++) {
for (int c = 0; c < cols; c++) {
if (grid[r][c] == 2) q.offer(new int[]{r, c});
else if (grid[r][c] == 1) fresh++;
}
}
int[][] dirs = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}};
int minutes = 0;
while (!q.isEmpty() && fresh > 0) {
int size = q.size();
for (int i = 0; i < size; i++) {
int[] cell = q.poll();
for (int[] d : dirs) {
int nr = cell[0] + d[0], nc = cell[1] + d[1];
if (nr >= 0 && nr < rows && nc >= 0 && nc < cols && grid[nr][nc] == 1) {
grid[nr][nc] = 2;
fresh--;
q.offer(new int[]{nr, nc});
}
}
}
minutes++;
}
return fresh == 0 ? minutes : -1;
}
}
#include <vector>
#include <queue>
#include <utility>
using namespace std;
class Solution {
public:
int orangesRotting(vector<vector<int>>& grid) {
int rows = grid.size(), cols = grid[0].size();
queue<pair<int, int>> q;
int fresh = 0;
for (int r = 0; r < rows; ++r)
for (int c = 0; c < cols; ++c) {
if (grid[r][c] == 2) q.push({r, c});
else if (grid[r][c] == 1) ++fresh;
}
const int dr[4] = {1, -1, 0, 0};
const int dc[4] = {0, 0, 1, -1};
int minutes = 0;
while (!q.empty() && fresh > 0) {
int sz = q.size();
while (sz--) {
int r = q.front().first, c = q.front().second;
q.pop();
for (int k = 0; k < 4; ++k) {
int nr = r + dr[k], nc = c + dc[k];
if (nr >= 0 && nr < rows && nc >= 0 && nc < cols && grid[nr][nc] == 1) {
grid[nr][nc] = 2;
--fresh;
q.push({nr, nc});
}
}
}
++minutes;
}
return fresh == 0 ? minutes : -1;
}
};
The loop condition while q and fresh > 0 is doing important work. It stops as soon as the last fresh orange rots, so we never count an extra "empty" minute where the final rotten oranges are dequeued but have nothing left to infect. It also means a grid with no fresh oranges returns 0 without entering the loop.
Complexity: each cell is enqueued at most once (we mark it rotten before enqueuing) and each dequeue checks four neighbours, so O(m·n) time. The queue can hold O(m·n) cells in the worst case. No separate visited array is needed because the grid itself records state. If the interviewer says you must not modify the input, copy the grid or keep a visited boolean matrix; complexity is unchanged.
Minute-by-Minute Dry Run
Grid from Example 1. Coordinates are (row, col). Initially the queue holds the one rotten orange (0,0), and fresh = 6.
| Level (minute) | Dequeued | Newly rotten | fresh after | minutes after |
|---|---|---|---|---|
| 1 | (0,0) | (1,0), (0,1) | 4 | 1 |
| 2 | (1,0), (0,1) | (1,1) from (1,0); (0,2) from (0,1) | 2 | 2 |
| 3 | (1,1), (0,2) | (2,1) from (1,1); (0,2) has no fresh neighbours | 1 | 3 |
| 4 | (2,1) | (2,2) | 0 | 4 |
| - | Loop stops because fresh == 0. Return 4. | |||
Notice that in level 2, cell (1,1) is adjacent to both (1,0) and (0,1). It is rotted and enqueued by whichever is dequeued first, and because it is immediately marked as 2, the second neighbour skips it. Without marking at enqueue time, (1,1) would enter the queue twice. In this tiny grid that only wastes work, but in large grids duplicate enqueues can blow the queue up.
For Example 2, the BFS rots everything reachable and stops when the queue empties. The orange at (2,0) has empty cells above it and to its right, so it is never reached. fresh ends at 1, and the function returns -1.
Counting Minutes Correctly: Three Ways
The off-by-one in the minute count is the most common bug. There are three clean ways to track time, and you should pick one and stick to it.
| Technique | How it works | Pitfall |
|---|---|---|
Level loop with fresh > 0 guard (above) | Process len(q) items per iteration, increment after each level | Without the guard, you count one extra level for the last batch |
| Store time in the queue | Enqueue (r, c, t); neighbours get t + 1; answer is the max t assigned to a fresh orange | Return 0 when no fresh oranges, not the initial t |
| Distance matrix | dist[r][c] = minute it rots; answer is max over cells that were fresh | Extra O(m·n) memory; must still detect unreachable cells |
The "time in the queue" version is often less error-prone under pressure because there is no level bookkeeping at all:
from collections import deque
def oranges_rotting_with_time(grid):
rows, cols = len(grid), len(grid[0])
q, fresh = deque(), 0
for r in range(rows):
for c in range(cols):
if grid[r][c] == 2:
q.append((r, c, 0))
elif grid[r][c] == 1:
fresh += 1
last = 0
while q:
r, c, t = q.popleft()
for nr, nc in ((r + 1, c), (r - 1, c), (r, c + 1), (r, c - 1)):
if 0 <= nr < rows and 0 <= nc < cols and grid[nr][nc] == 1:
grid[nr][nc] = 2
fresh -= 1
last = t + 1
q.append((nr, nc, t + 1))
return last if fresh == 0 else -1
Because BFS assigns times in non-decreasing order, the last assignment is the maximum, so last is the answer. If nothing was fresh, last stays 0.
Edge Cases Interviewers Check
| Grid | Expected | Why |
|---|---|---|
[[0]] | 0 | No oranges at all |
[[2,2],[2,2]] | 0 | All rotten already |
[[1]] | -1 | Fresh orange, no rotten source |
[[2,0,1]] | -1 | Empty cell blocks spread |
[[2,1,1,1,1]] | 4 | Single row, distance grows linearly |
[[2,1,1,1,2]] | 2 | Two sources meet in the middle; multi-source matters |
The last row is the one to dry-run aloud if you want to show you understand multi-source BFS. Starting from only the left orange would give 4; starting from both gives 2, because the middle orange is two steps from each end.
The Same Pattern: Walls and Gates, 01 Matrix and More
Multi-source BFS shows up whenever you need "distance to the nearest source" for every cell. Recognising this saves you from writing a BFS per cell (which is O((m·n)2) and a common wrong turn).
Walls and Gates (LeetCode 286)
Grid of -1 (wall), 0 (gate) and INF (empty room, 231 − 1). Fill each room with the distance to its nearest gate. Seed the queue with every gate. When you pop a cell at distance d, any neighbour still at INF gets d + 1 and is enqueued. Rooms that stay INF are unreachable.
from collections import deque
def walls_and_gates(rooms):
if not rooms:
return
INF = 2**31 - 1
rows, cols = len(rooms), len(rooms[0])
q = deque((r, c) for r in range(rows) for c in range(cols) if rooms[r][c] == 0)
while q:
r, c = q.popleft()
for nr, nc in ((r + 1, c), (r - 1, c), (r, c + 1), (r, c - 1)):
if 0 <= nr < rows and 0 <= nc < cols and rooms[nr][nc] == INF:
rooms[nr][nc] = rooms[r][c] + 1
q.append((nr, nc))
It is Rotting Oranges with the distance stored in the grid instead of a level counter. The "INF means unvisited" check plays the role of "fresh".
More problems with the same skeleton
| Problem | Sources | What you compute |
|---|---|---|
| LC 542 01 Matrix | All 0 cells | Distance of each 1 to nearest 0 |
| LC 1162 As Far from Land as Possible | All land cells | Max over water of distance to nearest land; -1 if all land or all water |
| LC 1765 Map of Highest Peak | All water cells (height 0) | Heights = distance to nearest water |
| LC 934 Shortest Bridge | Every cell of the first island (found by DFS) | BFS layers until you touch the second island |
| LC 1091 Shortest Path in Binary Matrix | Single source (top-left), 8 directions | Single-source cousin; good contrast |
| Escape the Spreading Fire (LC 2258) | All fire cells, then the person | Fire arrival times by multi-source BFS, then a BFS or binary search for the person |
For a broader set of grid problems, including flood fill and island counting, see matrix and grid interview problems and the island-counting walkthrough in LeetCode 200 Number of Islands. When edges have different weights, BFS no longer gives shortest paths and you need Dijkstra or 0-1 BFS, covered in shortest path algorithms explained.
Multi-source BFS outside grids
The trick is not grid-specific. Topological sort with Kahn's algorithm is a multi-source BFS: you seed the queue with every node of in-degree 0. "Find the nearest hospital for every city" on a road network with unit weights is the same pattern. In production systems, the same layered spreading model describes failure cascades or rumour propagation in a social graph, which is a useful sentence if an interviewer asks for real-world relevance. See topological sort interview problems for the Kahn's algorithm version.
How Rotting Oranges Shows Up in Interviews
This problem is popular in online assessments and first-round interviews at Amazon, Microsoft and many Indian product companies because it tests graph modelling on a grid with very little setup. The interviewer is looking for three things:
- Pattern recognition. Do you say "minimum time on an unweighted grid, so BFS" within the first minute? Do you seed all rotten oranges, rather than BFS from each?
- Correct bookkeeping. Fresh counter, marking on enqueue, level counting without the extra minute, the -1 and 0 cases.
- Explaining correctness. Why does one BFS from many sources give the nearest-source distance? The super-source argument is the cleanest answer.
Follow-up questions to expect
- "Return the grid of times instead of the max." Use the distance-matrix version.
- "Some oranges are immune." Treat them like empty cells (or like walls). Clarify whether immune oranges count toward the "all rotten" condition.
- "Rot takes 2 minutes to cross certain cells." Now edges have weights 1 or 2. Use Dijkstra, or split weighted edges into unit steps. 0-1 BFS applies only when weights are 0 or 1.
- "The grid is 105 × 105 and does not fit in memory." Discuss processing row bands, storing only the frontier, or that the honest answer is a distributed BFS (frontier-based, level synchronous). This is about reasoning, not code.
- "Can you do it without modifying the input?" Use a visited matrix or a copy.
Mistakes that cost candidates
- Running BFS separately from each rotten orange and taking minimums: correct but O(k·m·n).
- Marking cells as visited when dequeued instead of when enqueued: duplicates in the queue, and in the level-loop version, possibly double-decrementing
fresh. - Returning
minutes - 1as a hack to fix a level-counting bug, which then breaks the no-fresh-oranges case. - Forgetting that the queue can start empty (no rotten oranges) while fresh oranges exist: the answer is -1.
Frequently Asked Questions
Master Graph Patterns for Interviews
Prepflix's DSA program covers BFS, multi-source BFS and grid graphs with timed practice and mock interviews, so problems like Rotting Oranges become a two-minute pattern match.
Explore the Prepflix Course →