Coding Interview Patterns Module 20 – Graph patterns: traversal
Number of islands: flood fill and border regions
Count the islands of a grid with one flood fill per island, avoid recursion limits on long islands, and find the regions that can reach the border.
What you will learn
- Count the connected regions of a grid with one flood fill per region in O(rows × cols)
- Implement the flood fill with a queue or an explicit stack instead of deep recursion
- Find the cells that can reach the border by searching inwards from the edge
- Identify distinct island shapes with a canonical key
- Reproduce the common bugs of grid searches on the smallest maps that break them
Before you start
What you’ll learn · Before you start
What you will learn
- Count the connected regions of a grid with one flood fill per region in O(rows × cols)
- Implement the flood fill with a queue or an explicit stack instead of deep recursion
- Find the cells that can reach the border by searching inwards from the edge
- Identify distinct island shapes with a canonical key
- Reproduce the common bugs of grid searches on the smallest maps that break them
Before you start
On this page
Number of islands asks how many separate groups of land a grid holds, where two land cells belong together when a chain of land cells sharing sides joins them. Each island is a connected component of the grid’s graph, so the count is the number of searches it takes to mark all the land: one flood fill from every land cell that no earlier search reached. Grids show up in coding rounds as maps, boards, images and seating plans, and the same fill also answers which regions can reach the border.
The problem: islands on a survey map
The problem
A survey returns a map as rows strings of cols characters: '#' is land and '.' is water. Two land cells are
on the same island when you can walk from one to the other over land, one step at a time to a cell that shares a
side. Touching at a corner does not count. Return the number of islands. Limits: 1 ≤ rows, cols ≤ 1,000.
Three examples:
- The 5 × 8 survey map in the diagram has 5 islands. Islands C and E touch only at a corner, so they stay apart.
- A map of water only, such as three rows of
...., has 0 islands. - The single row
#.#.##has 3 islands: two single cells and a pair.
The survey map, every land cell lettered with its island
Text description of the diagram
The map has 5 rows, numbered 0 to 4 from the top, and 8 columns, numbered 0 to 7 from the left. Land cells are shaded and carry the letter of their island; water cells are plain.
- Island A, 3 cells: row 0, columns 0 and 1, and row 1, column 0.
- Island B, 4 cells: row 0, column 4; row 1, columns 4 and 5; row 2, column 5.
- Island C, 4 cells: row 2, column 2, and row 3, columns 1, 2 and 3.
- Island D, 4 cells: rows 2 and 3, column 7, and row 4, columns 6 and 7.
- Island E, 1 cell: row 4, column 4.
A dashed circle marks the corner where island C, at row 3, column 3, meets island E, at row 4, column 4. The two cells touch only diagonally, so they share no side and stay separate islands: the map has 5 islands. If diagonal contact counted, C and E would merge and the count would be 4.
The brute force: relabel until nothing changes
A method that needs no graph idea: give every land cell its own label, then sweep the map again and again, letting each land cell take the smallest label among itself and its land neighbours. When a sweep changes nothing, each island holds one label, so the answer is the number of distinct labels. It is correct, and on the survey map it takes 3 sweeps. The run then tries winding islands, full rows joined at alternate ends:
"""Islands by relabelling: every land cell takes the smallest label among itself and its land neighbours, sweep after
sweep, until a sweep changes nothing. Correct, but a winding island needs a sweep for almost every cell it has."""
SURVEY = [
"##..#...",
"#...##..",
"..#..#.#",
".###...#",
"....#.##",
]
def islands_by_relabelling(grid):
rows, cols = len(grid), len(grid[0])
label = [[r * cols + c if grid[r][c] == "#" else -1 for c in range(cols)] for r in range(rows)]
sweeps = 0
changed = True
while changed:
changed = False
sweeps += 1
for r in range(rows):
for c in range(cols):
if label[r][c] < 0:
continue
best = label[r][c]
for dr, dc in ((-1, 0), (1, 0), (0, -1), (0, 1)):
nr, nc = r + dr, c + dc
if 0 <= nr < rows and 0 <= nc < cols and 0 <= label[nr][nc] < best:
best = label[nr][nc]
if best < label[r][c]:
label[r][c] = best
changed = True
count = len({x for row in label for x in row if x >= 0})
return count, sweeps
def snake(k):
"""A k x k map holding one island that winds back and forth: full rows joined at alternate ends."""
grid = []
for r in range(k):
if r % 2 == 0:
grid.append("#" * k)
elif r % 4 == 1:
grid.append("." * (k - 1) + "#")
else:
grid.append("#" + "." * (k - 1))
return grid
count, sweeps = islands_by_relabelling(SURVEY)
print(f"Survey map, 5 x 8: {count} islands after {sweeps} sweeps")
print()
print("Winding islands:")
print(" k land cells sweeps cells read")
for k in (8, 16, 32, 64):
g = snake(k)
count, sweeps = islands_by_relabelling(g)
land = sum(row.count("#") for row in g)
print(f"{k:>5} {land:>11,} {sweeps:>7,} {sweeps * k * k:>11,} islands: {count}") Output
Survey map, 5 x 8: 5 islands after 3 sweeps
Winding islands:
k land cells sweeps cells read
8 36 16 1,024 islands: 1
16 136 62 15,872 islands: 1
32 528 250 256,000 islands: 1
64 2,080 1,010 4,136,960 islands: 1
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 labels_brute.py
Runs on this device, in your browser. The first run downloads Python (about 13.5 MB), which is kept for the next runs.
Your run, in this browser
The labels flow with the sweep along some rows and against it along others, one cell per sweep. So a winding island needs about k²/4 sweeps on a k × k map: 1,010 at k = 64, which read 4,136,960 cells. At 1,000 × 1,000 that is about 250,000 sweeps of a million cells, 2.5 × 10¹¹ cell visits of four neighbour checks each: hours, even in C++.
What to notice
An island is a set of cells that a single search reaches from any one of its cells. So start a flood fill from the first unmarked land cell, mark everything it reaches, add 1 to the count, and continue the scan. Each cell is marked once and looked at from its four neighbours, so the whole count is O(rows × cols), about 5 million steps at the limits. Border regions follow the same idea in reverse: one search started from every border cell at once marks everything that can reach the edge.
Flood fill: one search per island Pro: opens with a Pro, Premium or Ultimate pass
In this part:
- Why every island is counted exactly once: the invariant
- The code in five languages
- Deep islands: recursion that runs out of stack
- Searching inwards from the edge
- Lakes: water that cannot reach the edge
- Cells that drain to both coasts
- Distinct island shapes
- Variants: corner contact, areas, perimeters and islands that grow
- Edge cases and common bugs
- Exercise: land that cannot reach the edge
- Count the land cells that cannot reach the edge
- A practice set with answers
- Check yourself
- Interview questions
- Why does starting a flood fill from every unmarked land cell count each island exactly once?
- A recursive island counter passes every test, then crashes on a 1,000 × 1,000 satellite map whose land is one long winding strip. Explain the crash, estimate the depth, and rewrite the fill. How much memory does your version need in the worst case?
- A 1,000 × 1,000 map starts as water and receives 200,000 updates of the form "cell (r, c) is now land". After each update the app must report the number of islands. What do you use, what does one update cost, and what happens when the same cell is reported twice?
- A drone streams a map row by row: a million rows of 10,000 cells, far too much to store. Count the islands while reading, keeping only about two rows in memory. How do you avoid counting one island twice when two of its parts only meet many rows later?
- Key takeaways
8 runnable examples (C++, Go, Java, JavaScript, Python) · 1 exercise with sample tests · 3 interview answers · about 22 minutes
Opening this part with your pass…
This part opens with any MySmartCoPilot pass.
- Pro pass · 1 week₹79$2.99
- 30 days₹179$5.99
- 1 year₹1,699$45
One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.
Passes can’t be bought right now, so this part can’t be opened at the moment. The free part above stands on its own. If you have a pass, enter your licence key.
It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).
References
- depth-first search (Dictionary of Algorithms and Data Structures) (National Institute of Standards and Technology (NIST))
- breadth-first search (Dictionary of Algorithms and Data Structures) (National Institute of Standards and Technology (NIST))
- RecursionError (The Python Standard Library: built-in exceptions) (Python Software Foundation)
- sys.getrecursionlimit() (The Python Standard Library) (Python Software Foundation)
- Common sequence operations: notes on negative indexes (The Python Standard Library) (Python Software Foundation)
- collections: deque objects (The Python Standard Library) (Python Software Foundation)
- InternalError: too much recursion (MDN Web Docs)
- StackOverflowError (Java SE 25 and JDK 25 API) (Oracle)
- The Go Programming Language Specification: Index expressions (The Go Authors)
Related tools
Report a problem with this lesson
Kept only in this browser. Your Learn progress