Vibe Engines is free, interactive AI engineering & system design — runs in your browser. Pick a learning path →
Number of Islands
The canonical connected-components question — asked at every company that has ever run an interview. Flood-fill each unvisited patch of land, count how many times you had to start a flood. Grid traversal, visited bookkeeping, no tricks.
The problem
Given grid, a 2D list of 0s (water) and 1s (land), return the number of islands — groups of 1s connected horizontally or vertically (not diagonally). Traverse each island once with BFS or DFS, marking cells visited so no island is counted twice.
Your turn — write it
Edit the stub, hit Run (or ⌘/Ctrl + Enter), and watch the hidden tests. Stuck? open the hints under the problem, or Reveal solution is one click away.
Implement num_islands(grid) → the count of 4-connected groups of 1s, using BFS or DFS with a visited set (or by sinking visited land in a copy).
Examples and constraints 4 examples
grid = [[1,1,0],[0,1,0],[0,0,1]]2grid = [[1,0],[0,1]]2grid = [[0,0],[0,0]]0grid = [[1,0,1],[1,1,1]]1- Connectivity is 4-directional (up, down, left, right).
- Do not count an island more than once — mark visited cells.
- The grid may be empty; return 0.
Hints 4 ideas
- Scan every cell; when you find an unvisited 1, increment the count and flood from it.
- Flood with an explicit stack or queue: pop a cell, push its 4 unvisited land neighbours.
- Mutating a copy of the grid (set visited land to 0) is the simplest visited set.
- Bounds-check before you look: r in [0, rows), c in [0, cols).
Approach, complexity & discussion — open after you solve
Start from brute force
Without a shared visited set, the obvious method floods from every land cell: run a search from each 1, collect the set of cells it reaches, and count the distinct sets at the end. It is correct, because two cells produce the same set exactly when they are on the same island. But each flood can cost O(R · C) and there can be R · C land cells, so time is O((R · C)²), and storing the sets can take as much space. The tempting shortcut, counting land cells with no land above or to the left, is linear and wrong: the U-shaped [[1, 0, 1], [1, 1, 1]] is one island, and it counts two.
The approach
Scan every cell once and flood each island the first time you touch it. The reference num_islands returns 0 for an empty grid, copies it into g with [row[:] for row in grid] so the caller’s data survives, and walks r and c over every cell. When g[r][c] == 1 it has found an island nobody has counted: it adds 1 to count, sinks the cell to 0 and pushes it on stack. The inner loop pops a cell and checks the four offsets in ((1, 0), (-1, 0), (0, 1), (0, -1)); each in-bounds land neighbor is sunk at once and pushed.
Sinking at push time is the invariant that keeps it linear: a cell becomes 0 the moment it is discovered, so it is pushed at most once and can never start a second count. When the stack empties, the whole island is water in g and the scan moves on, so the number of floods started is the number of islands. The TypeScript numIslands is the same line for line.
Walk through an example
On the first example the scan starts one flood at the top-left corner, sinks the L-shaped island, and starts a second flood at the bottom-right cell.
| step | event | work | stack after | count |
|---|---|---|---|---|
| 1 | scan (0, 0) | land: count it, sink it, push it | [(0, 0)] | 1 |
| 2 | pop (0, 0) | neighbor (0, 1) is land: sink, push | [(0, 1)] | 1 |
| 3 | pop (0, 1) | neighbor (1, 1) is land: sink, push | [(1, 1)] | 1 |
| 4 | pop (1, 1) | no land neighbors left; the island is sunk | [] | 1 |
| 5 | scan (0, 1) to (2, 1) | every cell is now 0, so no flood starts | [] | 1 |
| 6 | scan (2, 2) | land: count it, sink it, push it | [(2, 2)] | 2 |
| 7 | pop (2, 2) | no land neighbors; the scan ends and returns 2 | [] | 2 |
Complexity
Time O(R · C): the scan reads each cell once, and each land cell is pushed and popped at most once with four neighbor checks. Space O(R · C) for the copy g plus the stack, which can also reach O(R · C): an all-land 100 × 100 grid peaks at 4,951 entries. Flooding from every land cell without a shared visited set is O((R · C)²) time.
Edge cases to test
[[1, 0], [0, 1]]→ 2. Diagonal cells do not touch; an eight-direction neighbor list returns 1.[]and[[]]→ 0. The guardif not grid or not grid[0]covers both; without it,len(grid[0])raisesIndexErroron the first.[[1, 0, 1], [1, 1, 1]]→ 1. The U shape joins along the bottom row; counting land cells with no land above or to the left gives 2.- A single row of 2,000 land cells → 1. A recursive flood fill goes about 2,000 calls deep and hits Python’s default recursion limit of 1,000; the explicit stack does not.
[["1", "1"], ["0", "1"]]→ 0 here. The best-known version of this problem stores land as the string"1"; this one uses the integer 1, so code written for one silently counts nothing on the other.- After
num_islands(grid)the caller’sgridis unchanged, because the reference sinks cells in the copyg. Sinking the input in place is quicker to write but destroys the caller’s data.
Common mistakes
- Re-counting cells of an island you already flooded — mark cells visited as you go (or mutate them to water).
- Deep recursion blowing the call stack on a large grid — prefer an iterative BFS/DFS.
- Using the wrong connectivity (4-directional vs 8-directional) for the problem.
Follow-ups an interviewer asks next
- Return the area of the largest island.
- Count cells inside each flood, adding one every time a cell is popped, and keep the maximum across floods. Nothing else changes: time stays O(R · C), and the extra state is two integers. The same hook yields every island’s size, which is the usual next question after counting them.
- Land appears one cell at a time. Report the island count after each addition.
- Rerunning the flood after every addition is O(R · C) per step. Use union-find instead: a new land cell is its own set and adds 1 to the count, then each union with a land neighbor that joins two different sets subtracts 1. With path compression and union by rank, each addition costs near-constant amortized time.
- The grid is too large for memory and arrives row by row.
- Label components with a row-by-row scan that keeps only the previous row’s labels and a union-find over those labels. Each land cell takes the label of a land neighbor above or to its left, uniting the two labels when both exist. A component with no cell in the current row is finished and can be counted, so memory is O(C) plus the live labels.
- Count islands with distinct shapes.
- During each flood, record every cell relative to the cell where the flood started, as
(y - r, x - c), sort the offsets, and add the resulting tuple to a set. Islands with the same shape produce the same tuple, so the answer is the set’s size. Counting rotations and reflections as equal means generating all eight transformed copies, shifting each to the origin, and keeping the smallest.
Where this shows up
This is connected-components labeling on a grid, and the same flood-fill drives image segmentation and blob detection, the paint-bucket tool, region labeling, and any “count or size the clusters” task on a graph.
Reference solution
Reference solution — Python
def num_islands(grid):
if not grid or not grid[0]:
return 0
rows, cols = len(grid), len(grid[0])
g = [row[:] for row in grid]
count = 0
for r in range(rows):
for c in range(cols):
if g[r][c] == 1:
count += 1
stack = [(r, c)]
g[r][c] = 0
while stack:
y, x = stack.pop()
for dy, dx in ((1, 0), (-1, 0), (0, 1), (0, -1)):
ny, nx = y + dy, x + dx
if 0 <= ny < rows and 0 <= nx < cols and g[ny][nx] == 1:
g[ny][nx] = 0
stack.append((ny, nx))
return count
print(num_islands([[1, 1, 0], [0, 1, 0], [0, 0, 1]])) # -> 2Reference solution — TypeScript
function numIslands(grid: number[][]): number {
if (grid.length === 0 || grid[0].length === 0) return 0;
const rows = grid.length, cols = grid[0].length;
const g = grid.map((row) => [...row]);
let count = 0;
for (let r = 0; r < rows; r++) {
for (let c = 0; c < cols; c++) {
if (g[r][c] === 1) {
count++;
const stack: [number, number][] = [[r, c]];
g[r][c] = 0;
while (stack.length) {
const [y, x] = stack.pop() as [number, number];
for (const [dy, dx] of [[1, 0], [-1, 0], [0, 1], [0, -1]]) {
const ny = y + dy, nx = x + dx;
if (ny >= 0 && ny < rows && nx >= 0 && nx < cols && g[ny][nx] === 1) {
g[ny][nx] = 0;
stack.push([ny, nx]);
}
}
}
}
}
}
return count;
}
console.log(numIslands([[1, 1, 0], [0, 1, 0], [0, 0, 1]])); // -> 2Questions about Number of Islands
Should Number of Islands use BFS or DFS?
Either works, and both visit each land cell once, so time is O(rows times columns) for both. The differences are memory and the call stack. A recursive DFS is shortest to write, but its depth can reach the size of the island, and Python’s default recursion limit of 1,000 frames is easy to exceed on a long island. An explicit stack, as in this solution, avoids that. BFS with a queue often holds less at once: on an all-land 100 by 100 grid, this solution’s stack peaks at 4,951 cells while a BFS queue from the same corner peaks at 100.
What is the time and space complexity of Number of Islands?
Time is O(R times C) for a grid with R rows and C columns. The outer scan reads every cell once, and each land cell is pushed and popped at most once during a flood, with four neighbor checks each. Space is O(R times C) in the worst case: this solution copies the grid so it can mark cells by setting them to 0, and the stack or queue can also grow in proportion to the grid. Mutating the input in place removes the copy but still leaves the stack, and it destroys the caller’s data.
Why are diagonal cells not connected in Number of Islands?
Because the problem defines islands with 4-connectivity: two land cells belong together only if they share an edge, meaning up, down, left or right. Cells that meet only at a corner, like the two 1s in [[1, 0], [0, 1]], are separate islands, so that grid has 2. Image processing uses both 4- and 8-connectivity, and switching between them changes only the neighbor list, from four offsets to eight. Under 8-connectivity the same grid is a single island, so confirm the rule before writing the loop.
Can you solve Number of Islands without modifying the input grid?
Yes. This solution copies the grid and sinks visited land in the copy, which leaves the caller’s data untouched at the cost of O(R times C) extra memory. A separate visited set or boolean matrix does the same job and works when the grid holds values you cannot overwrite. Union-find is a third option: give every land cell its own set, union each one with its land neighbors to the right and below, and count the sets that remain, an approach that also extends to grids that change over time.
Explore the topic
See this alongside everything else on the same subject — handbooks, system designs, challenges and tools, in one place.
More Challenges
- Two SumThe classic warm-up: find the two numbers that add up to a target. Brute force is O(n²) — a hash map gets you to one pass, O(n). Solve it in Python or TypeScript, right in your browser, with hidden tests and a reveal-solution button.Read →
- Valid ParenthesesThe canonical stack problem: decide whether every bracket is closed by the right type, in the right order. A stack turns nested matching into a single pass. Solve it in Python or TypeScript with hidden tests.Read →
- Fizz BuzzThe famous screening question. Print 1…n, but multiples of 3 become "Fizz", of 5 become "Buzz", and of both become "FizzBuzz". Easy — the catch is testing divisibility in the right order. Solve it in Python or TypeScript.Read →
- Merge IntervalsCalendar apps and memory allocators run on this: sort by start, sweep once, grow or close the current block. Touching endpoints merge, nested intervals vanish — the classic that punishes off-by-one thinking. Solve it in Python or TypeScript.Read →
- Top-K Frequent ElementsCount, then rank — the two-step pattern behind trending topics and hot-key detection. A hash map counts; a (-count, value) sort ranks with a clean tie-break; buckets get you to O(n) if you want the flex. Solve it in Python or TypeScript.Read →
- 3SumThe rite of passage: every unique zero-sum triplet, no duplicates, no O(n³). Sort once, fix one element, squeeze the rest with two pointers, and skip duplicates at all three levels. Solve it in Python or TypeScript.Read →