Two-Dimensional Arrays in a Recursive Java Class

Combining two-dimensional arrays with recursion is one of the most common patterns in algorithm interviews and real-world grid problems β€” flood fill, maze solving, Sudoku, connected-region counting. This guide walks through the pattern with working Java code.

Declaring a 2D array

A 2D array in Java is literally an array of arrays β€” each row is its own int[] object:

int[][] grid = {
    {1, 1, 0, 0},
    {0, 1, 0, 0},
    {1, 0, 0, 1}
};

int rows = grid.length;       // 3
int cols = grid[0].length;    // 4
int value = grid[1][2];       // 0 (row 1, col 2)

The recursive skeleton

Nearly every recursive grid algorithm follows the same four-step pattern:

  1. Bounds check β€” are we still inside the grid?
  2. Cell check β€” does this cell satisfy the condition we care about?
  3. Mark as visited β€” mutate the grid or a separate boolean[][].
  4. Recurse into the four (or eight) neighbours.

Example 1: counting connected regions

Given a grid where 1 is land and 0 is water, count the number of islands β€” groups of connected 1 cells (horizontally or vertically).

public class IslandCounter {
    public int countIslands(int[][] grid) {
        int count = 0;
        for (int r = 0; r < grid.length; r++) {
            for (int c = 0; c < grid[0].length; c++) {
                if (grid[r][c] == 1) {
                    sink(grid, r, c);
                    count++;
                }
            }
        }
        return count;
    }

    private void sink(int[][] grid, int r, int c) {
        // 1. bounds check
        if (r < 0 || r >= grid.length || c < 0 || c >= grid[0].length) return;
        // 2. cell check
        if (grid[r][c] != 1) return;
        // 3. mark as visited (by overwriting)
        grid[r][c] = 0;
        // 4. recurse into 4 neighbours
        sink(grid, r + 1, c);
        sink(grid, r - 1, c);
        sink(grid, r, c + 1);
        sink(grid, r, c - 1);
    }
}

Example 2: flood fill

Replace every cell connected to (sr, sc) that shares its original colour with a new colour β€” the classic paint bucket.

public void floodFill(int[][] image, int sr, int sc, int newColor) {
    int original = image[sr][sc];
    if (original == newColor) return; // avoid infinite recursion
    fill(image, sr, sc, original, newColor);
}

private void fill(int[][] img, int r, int c, int from, int to) {
    if (r < 0 || r >= img.length || c < 0 || c >= img[0].length) return;
    if (img[r][c] != from) return;
    img[r][c] = to;
    fill(img, r + 1, c, from, to);
    fill(img, r - 1, c, from, to);
    fill(img, r, c + 1, from, to);
    fill(img, r, c - 1, from, to);
}

Stack overflow risks

Recursive grid methods call themselves once per cell. For a 1000 Γ— 1000 grid, the recursion depth can reach one million calls, far beyond the default JVM stack size (~512 Kb).

Three ways to handle very large grids:

  • Increase stack size: java -Xss16m.
  • Run the recursion on a dedicated thread with a larger stack.
  • Convert to an iterative algorithm using an explicit Deque<int[]> stack or queue β€” safest option.

Iterative equivalent

private void sinkIterative(int[][] grid, int startR, int startC) {
    Deque<int[]> stack = new ArrayDeque<>();
    stack.push(new int[]{startR, startC});
    int[][] dirs = {{1,0}, {-1,0}, {0,1}, {0,-1}};
    while (!stack.isEmpty()) {
        int[] cell = stack.pop();
        int r = cell[0], c = cell[1];
        if (r < 0 || r >= grid.length || c < 0 || c >= grid[0].length) continue;
        if (grid[r][c] != 1) continue;
        grid[r][c] = 0;
        for (int[] d : dirs) stack.push(new int[]{r + d[0], c + d[1]});
    }
}

Best practices

  • Extract the 4 direction deltas into a int[][] dirs = {{1,0},{-1,0},{0,1},{0,-1}} array β€” cleaner than four explicit calls.
  • Use a separate boolean[][] visited when you cannot modify the input grid.
  • Always guard against original == newColor in flood fill to prevent infinite recursion.
  • Add a unit test with edge cases: empty grid, single cell, grid with no target value.

For 8-directional grids (diagonals), extend the direction array to 8 entries. The recursive pattern stays identical.