โ† Back to DevBytes

Solving Rotting Oranges in Python: Step-by-Step Guide

Introduction to the Rotting Oranges Problem

The Rotting Oranges problem is a classic graph traversal challenge frequently encountered in coding interviews and competitive programming. It models a real-world scenario where rot spreads through a grid of oranges, and your task is to determine how long it takes for all fresh oranges to rot โ€” or whether it's even possible.

At its core, this problem tests your understanding of Breadth-First Search (BFS), multi-source traversal, and matrix manipulation. Mastering it builds a strong foundation for solving similar problems involving flood fill, shortest paths in grids, and cellular automata simulations.

Problem Statement

You are given an m x n grid where each cell can have one of three values:

Every minute, any fresh orange that is 4-directionally adjacent to a rotten orange becomes rotten. You must return the minimum number of minutes that must elapse until no cell has a fresh orange. If this is impossible, return -1.

Why This Problem Matters

The Rotting Oranges problem is more than an interview exercise. It represents a class of problems where state changes propagate through a system over discrete time steps. Understanding it prepares you for:

These concepts appear in real-world applications such as wildfire spread modeling, virus propagation simulations, network broadcast protocols, and image processing algorithms.

Approach: Multi-Source BFS

The optimal strategy uses BFS because it explores nodes level by level, and each level corresponds to one minute of rotting. The key insight is that all initially rotten oranges act as simultaneous starting points.

Step-by-Step Algorithm

Complete Python Implementation

Below is a clean, well-commented implementation using Python's collections.deque for efficient queue operations.

from collections import deque
from typing import List

def orangesRotting(grid: List[List[int]]) -> int:
    if not grid or not grid[0]:
        return -1

    rows, cols = len(grid), len(grid[0])
    queue = deque()
    fresh_count = 0

    # Step 1: Find all rotten oranges and count fresh ones
    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_count += 1

    # Edge case: no fresh oranges to rot
    if fresh_count == 0:
        return 0

    # Step 2: BFS with level tracking
    directions = [(0, 1), (0, -1), (1, 0), (-1, 0)]
    minutes = 0

    while queue and fresh_count > 0:
        # Process all oranges at the current minute level
        level_size = len(queue)
        for _ in range(level_size):
            r, c = queue.popleft()
            for dr, dc in directions:
                nr, nc = r + dr, c + dc
                if (0 <= nr < rows and 0 <= nc < cols 
                        and grid[nr][nc] == 1):
                    grid[nr][nc] = 2
                    fresh_count -= 1
                    queue.append((nr, nc))
        minutes += 1

    # Step 3: Check if any fresh oranges remain unreachable
    return minutes if fresh_count == 0 else -1

Testing the Solution

Let's verify the implementation with a few test cases to ensure correctness.

if __name__ == "__main__":
    # Test case 1: Normal case
    grid1 = [[2,1,1],[1,1,0],[0,1,1]]
    print(orangesRotting(grid1))  # Expected: 4

    # Test case 2: Impossible โ€” isolated fresh orange
    grid2 = [[2,1,1],[0,1,1],[1,0,1]]
    print(orangesRotting(grid2))  # Expected: -1

    # Test case 3: No fresh oranges
    grid3 = [[0,2]]
    print(orangesRotting(grid3))  # Expected: 0

    # Test case 4: All fresh, no rotten
    grid4 = [[1,1],[1,1]]
    print(orangesRotting(grid4))  # Expected: -1

    # Test case 5: Single rotten orange
    grid5 = [[2,1,1,1],[1,1,1,1],[1,1,1,1]]
    print(orangesRotting(grid5))  # Expected: 6

Complexity Analysis

Understanding the time and space complexity helps you evaluate whether this solution scales to larger inputs.

This is optimal because you must examine every cell at least once to determine the answer.

Best Practices and Common Pitfalls

1. Use Deque, Not Lists

Always use collections.deque for BFS queues. Popping from the front of a Python list is O(n), while deque.popleft() is O(1). This difference becomes significant on large grids.

2. Track Fresh Count Explicitly

Instead of scanning the grid again at the end to check for remaining fresh oranges, maintain a fresh_count variable. This avoids an extra O(m ร— n) pass and makes the termination logic clearer.

3. Handle Edge Cases Early

Check for empty grids and grids with no fresh oranges before starting BFS. Returning early simplifies the main loop and prevents unnecessary work.

4. Mutate In-Place or Copy?

The implementation above mutates the grid in-place. If the caller needs the original grid preserved, create a deep copy first:

import copy

def orangesRottingPreserve(grid: List[List[int]]) -> int:
    grid_copy = copy.deepcopy(grid)
    # Run BFS on grid_copy instead
    return orangesRotting(grid_copy)

5. Avoid Re-processing Rotten Cells

By checking grid[nr][nc] == 1 before enqueuing, you ensure each fresh orange is added to the queue exactly once. Marking it rotten immediately upon enqueueing โ€” rather than upon dequeuing โ€” prevents duplicate entries.

Variations and Extensions

Once you understand the core solution, try these variations to deepen your mastery:

Conclusion

The Rotting Oranges problem elegantly combines BFS traversal with level-order timing to model a spreading process. By enqueuing all rotten oranges as simultaneous sources, tracking fresh oranges explicitly, and processing the queue level by level, you arrive at a clean O(m ร— n) solution. The patterns you learn here โ€” multi-source BFS, in-place grid mutation, and careful termination handling โ€” transfer directly to dozens of related problems in graph theory and grid-based simulation. Practice the variations, internalize the edge cases, and you'll be well equipped to tackle any spreading-process problem that comes your way.

๐Ÿ›  Tools from DevBytes

Inventory Tracker Pro โ€” Excel inventory system, low-stock alerts ยท $19
AI Dev Kit for Mac โ€” local AI dev environment templates ยท $9.99
KeyMapper for Mac โ€” custom keyboard shortcut toolkit ยท $7.99

โ† Back to all articles