Grid Algorithms· 20 min

Depth-First Search (DFS)

Go deep first — uses a stack

Setup

0 walls · 15×30 grid
Long straight corridors with single gaps. Clean, structured, easy to follow.
Step 1 / 88

Current step

1 / 88

Initialize Depth-First Search. Start=7,4, End=7,25.

Step log

Tip: pick a tool, then click or drag on the grid. Walls block the path; start and end are draggable. Hit Generate maze for a structured layout.

Start
End
Wall
Visited
Frontier
Current
Path
dfs.java
1List<Cell> dfs(Cell[][] grid, Cell start, Cell end) {
2 Deque<Cell> stack = new ArrayDeque<>(List.of(start));
3 Set<Cell> visited = new HashSet<>(Set.of(start));
4 Map<Cell, Cell> parent = new HashMap<>();
5 while (!stack.isEmpty()) {
6 Cell cur = stack.pop();
7 if (cur.equals(end)) return reconstruct(parent, end);
8 for (Cell n : neighbors(cur)) {
9 if (!visited.contains(n) && !n.isWall) {
10 visited.add(n); parent.put(n, cur);
11 stack.push(n);
12 }
13 }
14 }
15 return null;
16}
● Java

Depth-First Search

Explores as deep as possible along each branch before backtracking, using a stack. Does NOT guarantee the shortest path.

Time
O(V + E)
Visited
0
Frontier
1
Path length
0

Written guide

Depth-First Search (DFS): complete guide

DFS on a 2D grid dives from a cell into every connected neighbor before backing out, which makes it the natural tool for flood fill and counting islands. You will also see why the paths it finds are not shortest and how to avoid stack overflow on large grids.

Intuition

Think of the paint bucket tool in an image editor. You click one pixel, and the colour spreads to every touching pixel of the same colour, then to their touching pixels, until the region is filled. The tool does not care about distance; it only cares about connectivity. That is the job grid DFS does best: given one cell, find everything connected to it.

Counting islands builds on that. Scan the grid cell by cell. Whenever you land on unvisited land, you have found a new island; run DFS from that cell to sink the whole island so later scans do not count it again. The number of times you start a DFS is the answer, exactly like counting connected components in Graph DFS.

How it works step by step

  1. Loop over every cell (r, c) in row-major order.
  2. If the cell is land ('1') and not yet visited, increment the island counter and call sink(r, c).
  3. Inside sink: return immediately if the cell is out of bounds, water, or already visited. This base case at the top keeps the neighbor code simple.
  4. Mark the cell visited, for example by overwriting it with '0'.
  5. Recurse into the four neighbors: up, down, left, right.
  6. When the outer loop finishes, the counter holds the number of islands. For flood fill, skip the counter and replace the colour while marking.

Dry run: counting islands

Grid rows are 1 1 0, 0 1 0, 1 0 1. Visited land is sunk to 0. The stack column shows the pending recursive calls on land cells.

StepCell / stackActionIslands
1(0,0) / [(0,0)]Scan finds land, new island, start DFS, sink (0,0)1
2[(0,0), (0,1)]Up, down (1,0) and left are not land; right (0,1) is land, sink it1
3[(0,0), (0,1), (1,1)]From (0,1), down (1,1) is land, sink it1
4[](1,1) has no land neighbors left; the stack unwinds1
5(0,1)..(1,2)Scan continues; these cells are now 0 or were water1
6(2,0) / [(2,0)]New land, new island, sink it; no land neighbors2
7(2,2) / [(2,2)]New land, new island, sink it; (2,1) and (1,2) are water3

The answer is 3. Note that (2,0) and (1,1) touch only diagonally, so with 4-direction connectivity they are separate islands. If the problem counted diagonals, (2,0), (1,1) and (2,2) would merge and the answer would be 1.

Code

NumberOfIslands.java
public class NumberOfIslands {
    public int numIslands(char[][] grid) {
        int islands = 0;
        for (int r = 0; r < grid.length; r++) {
            for (int c = 0; c < grid[0].length; c++) {
                if (grid[r][c] == '1') {
                    islands++;
                    sink(grid, r, c);
                }
            }
        }
        return islands;
    }

    private void sink(char[][] grid, int r, int c) {
        if (r < 0 || r >= grid.length || c < 0 || c >= grid[0].length) return;
        if (grid[r][c] != '1') return;       // water or already visited
        grid[r][c] = '0';                    // mark before recursing
        sink(grid, r - 1, c);
        sink(grid, r + 1, c);
        sink(grid, r, c - 1);
        sink(grid, r, c + 1);
    }
}
grid_dfs.py
def flood_fill(image, sr, sc, color):
    """Iterative DFS flood fill (LeetCode 733); safe on very large regions."""
    original = image[sr][sc]
    if original == color:          # nothing to do, and avoids an infinite loop
        return image
    rows, cols = len(image), len(image[0])
    stack = [(sr, sc)]
    image[sr][sc] = color
    while stack:
        r, c = stack.pop()
        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 image[nr][nc] == original:
                image[nr][nc] = color      # recolouring doubles as the visited mark
                stack.append((nr, nc))
    return image


def max_area_of_island(grid):
    """Same stack-based DFS, returning the size of the largest island."""
    rows, cols, best = len(grid), len(grid[0]), 0
    for r in range(rows):
        for c in range(cols):
            if grid[r][c] != 1:
                continue
            grid[r][c], stack, area = 0, [(r, c)], 0
            while stack:
                cr, cc = stack.pop()
                area += 1
                for nr, nc in ((cr - 1, cc), (cr + 1, cc), (cr, cc - 1), (cr, cc + 1)):
                    if 0 <= nr < rows and 0 <= nc < cols and grid[nr][nc] == 1:
                        grid[nr][nc] = 0
                        stack.append((nr, nc))
            best = max(best, area)
    return best


print(flood_fill([[1, 1, 1], [1, 1, 0], [1, 0, 1]], 1, 1, 2))  # [[2,2,2],[2,2,0],[2,0,1]]
print(max_area_of_island([[1, 1, 0], [0, 1, 0], [1, 0, 1]]))   # 3

Time and space complexity

MeasureCostWhy
TimeO(R x C)Each cell is marked once and inspects four neighbors
Extra space (in-place marking)O(R x C) worst caseThe recursion or explicit stack can hold every cell when the whole grid is one snake-shaped island
Extra space (typical)Proportional to island sizeOnly the current island's cells can be on the stack

The worst case is real. A 1000 x 1000 grid of all land can drive recursion a million frames deep. Java's default thread stack usually fails long before that, and CPython stops at about 1000 frames unless you raise sys.setrecursionlimit, which only moves the crash to the C stack. The explicit stack in the Python code lives on the heap and has no such limit.

Edge cases

  • Empty grid or zero columns: guard grid[0] before reading its length.
  • All water: the outer loop finishes with 0 islands and never recurses.
  • All land: one island, and the deepest possible recursion; this is the test that exposes stack overflows.
  • Flood fill with the same colour: if the new colour equals the old one, return early or the marking never distinguishes visited cells.
  • Islands touching the border: problems like Surrounded Regions or Number of Enclaves treat them differently, so start DFS from the border first.

Common mistakes

  • Marking a cell after the recursive calls instead of before. Two neighbors then recurse into each other forever.
  • Comparing the char grid with the integer 1 in Java, or the int grid with the string '1' in Python. LeetCode #200 uses characters.
  • Using DFS to answer a shortest-path question. It finds a path quickly, but not the shortest.
  • Writing four nearly identical bounds checks in four places instead of one check at the top of the function.
  • Counting diagonal neighbors when the problem defines adjacency as horizontal and vertical only, or the reverse.

Variations: why DFS paths are not shortest

On an open 3 x 3 grid from the top-left to the top-right corner, a DFS that tries down and up before left and right will plunge to the bottom of the first column and then zigzag back up, a path of 6 moves when 2 would do. DFS commits to the first direction that works and only backtracks at a dead end, so the first route it finds depends on direction order, not length. Finding the true minimum with DFS means exploring every path, which is exponential. That is why reachability, region size, perimeter and connectivity belong to DFS, while minimum moves belong to BFS on a grid. Backtracking problems such as Word Search are DFS that un-marks a cell on the way back out so it can be used by another path.

  • #200 Number of Islands — the lesson's main pattern: count how many times the scan starts a DFS.
  • #733 Flood Fill — DFS recolours one connected region in place.
  • #695 Max Area of Island — DFS returns the size of each island and you keep the maximum.
  • #130 Surrounded Regions — DFS from border O cells marks the ones that must survive.
  • #417 Pacific Atlantic Water Flow — run DFS uphill from each ocean's border and intersect the results.
  • #1020 Number of Enclaves — sink border-connected land first, then count what is left.
  • #79 Word Search — backtracking DFS that unmarks cells when it returns.

FAQ

›Should I use DFS or BFS for Number of Islands?

Either works in O(R x C). DFS is shorter to write; BFS or an explicit stack avoids recursion depth problems on very large islands. Union-find is a third option.

›Why does recursive DFS cause a stack overflow on big grids?

Each recursive call uses a stack frame, and a single large island can require one frame per cell. Convert to an explicit stack or BFS when grids can be large.

›Is it okay to modify the input grid to mark visited cells?

It is the standard O(1) extra-space trick and is accepted on LeetCode, but say so in an interview and offer a separate visited array if the input must be preserved.

›Does DFS find the shortest path in a maze?

No. It finds some path, which may be much longer than the shortest one. Use BFS for minimum steps.

›What is the difference between flood fill and number of islands?

Flood fill runs one DFS from a given cell and recolours its region. Number of islands runs a DFS from every unvisited land cell and counts how many runs were needed.