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:
- Bounds check β are we still inside the grid?
- Cell check β does this cell satisfy the condition we care about?
- Mark as visited β mutate the grid or a separate
boolean[][]. - 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[][] visitedwhen you cannot modify the input grid. - Always guard against
original == newColorin 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.