Algorithm

Max Area of Island

Depth-First Search (DFS) Pattern

Max Area of Island

You are given an m x n binary grid where 1 represents land and 0 represents water.

An island is a maximal group of land cells connected horizontally or vertically — diagonal contact does not join cells. The area of an island is the number of land cells it contains.

Return the area of the largest island in the grid, or 0 if the grid contains no land at all.

CONSTRAINTS
  • m == grid.length, n == grid[i].length
  • 1 <= m, n <= 50
  • grid[i][j] is 0 or 1
  • Cells touching only at a corner belong to different islands.
  • Return 0, not -1 or an error, when there is no land.
EXAMPLE 1
Input: grid = [[0,0,1,0,0],[0,0,1,1,0],[0,1,1,0,0]]
Output: 5
The land cells (0,2), (1,2), (1,3), (2,2) and (2,1) form a single connected chain — (0,2) joins (1,2) vertically, which joins both (1,3) horizontally and (2,2) vertically, which joins (2,1). Five cells, so the area is 5.
EXAMPLE 2
Input: grid = [[1,0,1],[0,0,0],[1,0,1]]
Output: 1
Four land cells sit in the corners with water between them. No two are orthogonally adjacent, so each is its own island of area 1, and the largest is 1. Counting all the land would wrongly give 4 — the answer is per-island, not a total.
EXAMPLE 3
Input: grid = [[1,1],[1,1]]
Output: 4
All four cells are mutually connected, forming one island covering the whole grid.
EXAMPLE 4
Input: grid = [[0,0,0,0,0]]
Output: 0
No land exists, so no island exists, and the specified answer for that case is 0.
Is the area a count of cells or something like a perimeter?
A count of cells. Perimeter is a different and popular variant, so it is worth saying which one you are computing.
Do diagonally adjacent land cells belong to the same island?
No, only orthogonal adjacency connects them. A diagonal chain of land is a sequence of separate area-1 islands under this rule.
What should I return when the grid has no land?
0. Initialising your running maximum to 0 handles this with no special case, whereas initialising to something like negative infinity would need one.
May I modify the grid as I go?
Usually yes, and it makes marking visited cells free. If the grid must be preserved, use a separate visited set — same complexity, extra memory.

This problem sits one small step beyond simply counting islands, and that step is the interesting part. Counting groups needs nothing but the knowledge that a group exists. Measuring them needs each traversal to come back carrying a number. How information travels back out of a recursive walk is the idea worth taking away.

Where the naive attempt goes wrong

The obvious plan is to stand on every land cell and count how much land connects to it, keeping the biggest result.

python
best = 0
for r, c in all_cells:
    if grid[r][c] == 1:
        best = max(best, count_connected(r, c))    # a fresh count from every cell

Two things are wrong. The smaller problem is cost: an island of size k triggers k separate counts, each of which walks all k cells, so a single large island costs k² and the grid overall approaches O((m × n)²).

The larger problem is that count_connected does not work at all unless it tracks where it has been. Without that, it walks from a cell to its neighbour and straight back again, forever. And once you add a visited set, you must clear it between starting cells, which is more work again.

Name the waste: every cell of an island independently recomputes the size of the island it belongs to. That size is a property of the island, not of the cell. It should be computed once, when the island is first discovered.

The reframe: measure while you consume

Combine the two jobs. Scan the grid for land not yet seen. Each time you find some, you have found an island nobody has measured yet — so walk it, and while walking, sink every cell you enter so it can never be found again. Count the cells as you sink them. When the walk returns, you have that island's exact area and it can never be counted twice.

The outer scan and the inner walk again have completely separate jobs. The scan finds one new island at a time; the walk consumes and measures exactly one island.

That much mirrors plain island counting. The new part is how the count gets back to the caller.

Information flows up as return values

The walk is recursive, and a recursive call is a function call like any other, which means it can return a value. That is the mechanism for carrying information back out.

Think about what one cell can honestly report. A cell that is water or off the grid contributes nothing, so it reports 0. A land cell contributes itself, worth 1, plus everything its four neighbours report. It does not need to know anything about the island's global shape; it only adds its own 1 to the four numbers handed back to it.

python
def max_area_of_island(grid):
    rows, cols = len(grid), len(grid[0])

    def area(r, c):
        if r < 0 or r >= rows or c < 0 or c >= cols or grid[r][c] == 0:
            return 0                     # base case: contributes nothing
        grid[r][c] = 0                   # sink BEFORE recursing
        return (1
                + area(r + 1, c)
                + area(r - 1, c)
                + area(r, c + 1)
                + area(r, c - 1))

    best = 0
    for r in range(rows):
        for c in range(cols):
            if grid[r][c] == 1:
                best = max(best, area(r, c))
    return best

That is the whole difference from a plain island count: the traversal returns a number instead of returning nothing, and the caller keeps a running maximum. The skeleton is otherwise unchanged.

Why is the arithmetic correct? Because sinking on entry guarantees each land cell is entered exactly once across the whole traversal, so each cell contributes its 1 to exactly one call's sum. The sums nest — a call's result includes the results of everything below it — and the outermost call therefore totals every cell in the island, once each.

Crucial Notegrid[r][c] = 0 comes before the four recursive calls, never after. Reverse them and cell A calls neighbour B while A is still land, so B calls back into A, and the two recurse into each other until the stack overflows. Sinking on entry is what guarantees the recursion terminates — and it is also what makes the count correct, since a cell entered twice would contribute 2.
Crucial Notebest must be updated in the outer scan, not inside area. The recursive function returns partial sums as it unwinds — a call deep inside a large island might return 3 for its own sub-branch — and comparing those against a running maximum is meaningless. Only the value returned to the outer loop is a complete island area.

Every cell is entered at most once by a traversal and looked at once by the scan, so the total is O(m × n) time, with recursion depth up to O(m × n) in the worst case of one snaking island.

Worked example

Grid:

text
1 1 0
0 0 0
1 1 1

The scan reaches (0,0), which is land, so area(0,0) runs.

- area(0,0): in bounds and land, so sink it and start summing 1 plus four calls.
- Down, area(1,0): holds 0. Returns 0.
- Up: off the grid. Returns 0.
- Right, area(0,1): land, so sink it and sum 1 plus its four calls. Down is (1,1), water, 0. Up is off-grid, 0. Right is (0,2), water, 0. Left is (0,0), already sunk, 0. So it returns 1 + 0 + 0 + 0 + 0 = 1.
- Left: off the grid. Returns 0.
- Total: 1 + 0 + 0 + 1 + 0 = 2.
- best becomes 2.

The scan continues. (0,1) now reads as water — it was sunk — so it is skipped, which is exactly what stops this island being measured a second time. Row 1 is all water.

- (2,0) is land, so area(2,0) runs. It sinks (2,0), and its right call reaches (2,1), which sinks and calls right again to (2,2), which sinks and finds nothing further. (2,2) returns 1. (2,1) returns 1 + 1 = 2. (2,0) returns 1 + 2 = 3.
- best becomes max(2, 3) = 3.

The scan finishes over an empty grid. Answer 3.

For the corner-only grid [[1,0,1],[0,0,0],[1,0,1]], each traversal sinks a single cell and finds water in all four directions, so each returns 1 and best stays 1 — even though four land cells exist in total. Area is measured per island, never summed across them.

The pattern, extracted

The skeleton here is the same connected-region traversal used to fill a region or count islands. Only one slot changed: what each traversal reports. That slot is the whole design space.

- Report nothing, and count how many times you launched: the number of islands.
- Report 1 per cell and sum: the area, this problem.
- Report 1 per cell but only when a neighbour is water or off-grid, and sum: the perimeter.
- Report the minimum and maximum row and column touched: the island's bounding box.
- Report the set of cells visited, and check whether any sits on the border: Surrounded Regions.

The underlying principle is worth stating plainly, because it generalises far beyond grids: information travels down a recursion as arguments and travels back up as return values. When each part of a structure must contribute to a total, give the recursive call a return type and let the sums nest. When a decision instead depends on where you came from, pass it down as a parameter. Nearly every tree and graph recursion is one of these two shapes, or both at once.

Recognition signals: largest region, size of each group, total cells in a cluster — any question about a per-component quantity rather than the mere existence of components. Depth-first search is used rather than breadth-first because the question is about totals, not distances; a queue-based version visits identical cells and gives an identical answer, and is the safer choice when the grid is large enough for recursion depth to matter.

Train the reflex: when a problem asks for a property of each group rather than how many groups, keep the same traversal and change what it hands back.

Interactive Strategy Visualization
Magnitude Tracker

Aggregate DFS

Value-Returning Traversal

Local Area
0Current
Grid Max
0Peak
0
L
L
0
0
L
L
0
0
0
0
0
0
L
L
0
0
L
L
L
0
0
L
L
L
Phase
1/8

"Initiating grid-wide scan for land clusters."

Pedagogical PivotRecursive Aggregation

Instead of just marking nodes, the DFS transforms into a value generator. Each call adds 1 to the sum returned by its children, rolling up the total area like a chain reaction.

O((M × N)²) Recount Island Per Cell
O(M × N) Traversal Returning A Size
O(M × N) Iterative Traversal With Explicit Stack