HARD Lab · Graphs · 41

200. Number of Islands

Count the connected components of "1" cells in a binary grid by BFS flood-filling each unvisited land cell — the moment you discover a new 1without visiting it first, you've found a new island.

MediumFlood FillBFS/DFSTypeScript

PROBLEM What we're solving

Given a 2-D grid of "1" (land) and "0" (water), count the number of islands. An island is a group of horizontally or vertically connected land cells, surrounded by water or grid boundaries.

Example:
["1","1","0","0"]
["1","1","0","0"]
["0","0","1","0"]
["0","0","0","1"]
→ output 3. Top-left 2×2 block is island 1; the lone cell at (2,2) is island 2; the lone cell at (3,3) is island 3.

KEY IDEA Every new unvisited "1" is exactly one new island

Insight → You never need to count cells — you count times you start a new flood fill. When your outer scan hits an unvisited "1", increment the counter once and immediately flood-fill the entire connected component, marking every reachable land cell visited. The next unvisited "1" your scan finds is guaranteed to belong to a brand-new island — no merging, no double-counting.

RECIPE Scan → find → flood → repeat

  • 0 · Outer double loop. Iterate every cell (r, c) in row-major order. This ensures no cell is ever skipped.
  • 1 · Land check. If grid[r][c] === '1' and not yet visited, this is a new island. Increment count.
  • 2 · Flood fill (BFS). Enqueue (r, c) and mark it visited immediately. For each dequeued cell, check its 4 neighbors; enqueue any unvisited land neighbor and mark it visited right away (before it is dequeued) to prevent duplicate enqueues.
  • 3 · Mark visited. Mutate the grid to '0' in-place (O(1) space), or use a separate visited boolean grid (safer if the input must not be mutated).
  • 4 · Return count. When the outer loop finishes, every connected component has been counted exactly once.
Classic confusion → Marking a cell visited when you dequeue it (instead of when you enqueue it) causes the same cell to be added to the queue multiple times via different neighbors, leading to wrong counts or TLE on dense grids. Always mark visited before pushing into the queue.

COST Complexity & alternatives

Brute force (no visited set)
O(R·C)²
Re-scanning from scratch for each cell is catastrophic.
BFS / DFS flood fill
O(R·C)
Each cell is visited at most once. O(min(R,C)) queue space.

Both BFS and DFS give identical complexity. BFS uses a queue (iterative, no call-stack risk); DFS is slightly shorter to write but can blow the call stack on grids that are entirely land. Union-Find is a third option that gives the same O(R·C·α) asymptotic (practically O(R·C)) and is useful when the grid is updated dynamically (see Number of Islands II).

Pattern transfer → The same flood-fill skeleton solves Max Area of Island (accumulate size during fill), Surrounded Regions (flood fill from border, then flip), Pacific Atlantic Water Flow (two flood fills from opposite borders), and Rotting Oranges (multi-source BFS in one pass).

RUN IT BFS flood-fill each unvisited land mass

step 0 / 10
STARTScan every cell. On each unvisited 1, start a new BFS flood-fill to count one island.
1
1
~
~
1
1
~
~
~
~
1
~
~
~
~
1
State
islands: 0
current cell (BFS head)visited land (same island)
slowfast

TYPESCRIPT The solution, annotated

numIslands.ts
function numIslands(grid: string[][]): number {
  const rows = grid.length;
  const cols = grid[0].length;
  let count = 0;

  function bfs(r: number, c: number): void {
    const queue: [number, number][] = [[r, c]];
    grid[r][c] = '0';                  // mark visited in-place
    const dirs: [number, number][] = [[-1,0],[1,0],[0,-1],[0,1]];
    while (queue.length > 0) {
      const [cr, cc] = queue.shift()!;
      for (const [dr, dc] of dirs) {
        const nr = cr + dr;
        const nc = cc + dc;
        if (nr >= 0 && nr < rows && nc >= 0 && nc < cols && grid[nr][nc] === '1') {
          grid[nr][nc] = '0';          // mark before enqueue prevents duplicates
          queue.push([nr, nc]);
        }
      }
    }
  }

  for (let r = 0; r < rows; r++) {
    for (let c = 0; c < cols; c++) {
      if (grid[r][c] === '1') {
        count++;
        bfs(r, c);
      }
    }
  }
  return count;
}

Reading it block by block

Lines 2–3 — grid dimensions. Cache rows and cols once so every bounds check is a simple comparison.
Lines 6–19 — BFS flood fill. Seed the queue with the start cell and immediately mark it '0'. The inner loop dequeues, checks all four neighbors, and marks + enqueues any unvisited land. Marking on enqueue (line 15) prevents the same cell from entering the queue via two different neighbors.
Lines 22–28 — outer scan. Row-major iteration hits every cell. When a live '1' is found, the counter increments once and the BFS erases the whole island. No cell will ever trigger the counter twice.
Line 29 — return count. After the scan, count equals the number of connected components. The grid has been flattened to all '0's as a side effect (in-place mutation).
Complexity → O(R·C) time — every cell is enqueued at most once. O(min(R,C)) space for the BFS queue in the worst case (a long thin island along the diagonal).

INTERVIEWFollow-ups they'll ask

  • "Can you avoid mutating the input grid?" Use a separate visited: boolean[][] array initialized to false. Same complexity; no side effects.
  • "What if the grid is updated online (cells flip from water to land)?" Switch to Union-Find with path compression — merging two cells is O(α), and you maintain a running component count. This is exactly Number of Islands II (LC 305).
  • "What about diagonal connectivity?" Expand the directions array from 4 entries to 8 (add the four diagonals). The rest of the algorithm is unchanged.
  • "Return the area of the largest island instead of the count?" Have each BFS call return a cell count; track the running maximum. This is Max Area of Island (LC 695).
  • "What's the brute-force?" For each unvisited '1' do a fresh DFS without marking — O((R·C)²) in the worst case. The flood-fill brings it to O(R·C) by guaranteeing each cell is touched at most once.

MNEMONIC The one-liner

"See an unvisited 1? That's an island — stamp the whole blob, then keep scanning."

TRIGGERS When you see ___ → reach for ___

"count connected regions" in a gridBFS/DFS flood fill + visited set
grid of 0/1 with 4-directional adjacencyouter scan → fill on unvisited 1
"islands update dynamically / streaming cells"Union-Find with running count
"area of each island" instead of countsame fill, return cell count

SKELETON The reusable shape

skeleton.ts
function numIslands(grid: string[][]): number {
  const rows = grid.length, cols = grid[0].length;
  let count = 0;
  function bfs(r: number, c: number): void {
    const queue: [number, number][] = [[r, c]];
    grid[r][c] = '0';
    while (queue.length > 0) {
      const [cr, cc] = queue.shift()!;
      for (const [dr, dc] of [[-1,0],[1,0],[0,-1],[0,1]] as [number,number][]) {
        const nr = cr + dr, nc = cc + dc;
        if (nr >= 0 && nr < rows && nc >= 0 && nc < cols && grid[nr][nc] === '1') {
          grid[nr][nc] = '0';
          queue.push([nr, nc]);
        }
      }
    }
  }
  for (let r = 0; r < rows; r++)
    for (let c = 0; c < cols; c++)
      if (grid[r][c] === '1') { count++; bfs(r, c); }
  return count;
}

FLASHCARDS Tap to flip

Why increment the island counter before calling BFS, not after?
The BFS erases the island (marks cells visited). You need to count it before it disappears.
tap to flip
1 / 7
Answer to reveal explanations. No penalty for retries.Score: 0 / 7
QUESTION 01
What is the time complexity of the BFS flood-fill solution on an R × C grid?
QUESTION 02
When should you mark a cell as visited — on enqueue or on dequeue?
QUESTION 03
How many islands are in this grid?
["1","1","0","0"]
["1","1","0","0"]
["0","0","1","0"]
["0","0","0","1"]
QUESTION 04
You need to solve this problem on a grid that must not be mutated. What is the cleanest fix?
QUESTION 05
Which follow-up problem uses Union-Find instead of BFS/DFS flood fill?
QUESTION 06
A grid has all "1"s (every cell is land). How many islands are there?
QUESTION 07
What modification to the directions array lets you count islands with 8-directional (diagonal) connectivity?