Flood fill
Starting from one cell of a grid, spread to every touching cell of the same color, like the paint bucket in a drawing app.
O(rows * cols)Space O(rows * cols)7 practice problemsStep-through pictureThe idea
A grid, like the pixels of an image or the cells of a spreadsheet, is a graph in disguise. Each cell is a node, and each cell is connected to the cells directly above, below, left, and right of it.
Flood fill starts at one cell and finds every cell connected to it that has the same color. It's what the paint bucket tool does: click one pixel, and the whole same-colored region changes color.
It's a DFS or BFS on the grid. You keep a list of cells still to check (a stack here), change each one's color when you reach it, and add its four neighbors that still have the old color. Changing the color doubles as the "visited" mark, so no extra array is needed.
An everyday example
Spilled water on a tiled floor
Water spilled on one tile spreads to the tiles touching it, but only to tiles at the same level. A raised tile stops it. The water keeps spreading until every reachable tile at that level is wet. A tile that is already wet doesn't need to be wet again.
| In the example | In the code |
|---|---|
| The tile where the water lands | The start cell grid[row][col] |
| Tiles at the same level | Cells equal to oldColor |
| A tile getting wet | grid[nr][nc] = newColor |
| Water about to spread from a tile | A cell waiting on the stack |
| A raised tile or the edge of the room | A different color or a position outside the grid |
From the slow way to flood fill
- The obvious way to fill a region: paint the start cell, then scan the whole grid again and again, painting any
oldColorcell that touches a painted cell, until a full scan paints nothing. A long winding region may grow by only one cell per scan, so this can cost(rows * cols)scans ofrows * colscells each. - Most of those scans look at cells that can't change. A cell can only become paintable at the moment one of its four neighbors is painted. So when you paint a cell, only its four neighbors are new candidates, and nothing else needs to be looked at.
- Keep those candidates in a to-do list. A stack is the simplest: push a cell when it is painted, and later pop it to check its four neighbors. Any neighbor that still has
oldColorgets painted and pushed in turn. - Painting doubles as the "already done" mark, because a painted cell no longer equals
oldColor. That only holds if the two colors differ, so return early when they don't. That loop is flood fill.
How it works
- Read
oldColor, the color of the start cell. If it already equalsnewColor, there is nothing to do. - Paint the start cell with
newColorand put it on thestack. - While the stack has cells, pop one,
[r, c]. - For each of the four directions
[dr, dc], compute the neighbornr = r + dr,nc = c + dc. - Skip the neighbor if it is outside the grid or its color is not
oldColor. - Otherwise paint it with
newColorand push it, so its own neighbors get checked later. - When the stack is empty, the whole region has been painted.
Watch it run
This picture runs the code below on a real input. Press Step to move one step at a time, or Play to watch. The highlighted line is the one that just ran. Change the input to see other cases.
See every step at onceThe same run, laid out side by side
The code
Point at a note to light up the lines it explains.
function floodFill(grid, row, col, newColor) {const oldColor = grid[row][col];if (oldColor === newColor) return grid;const stack = [[row, col]];grid[row][col] = newColor;while (stack.length > 0) {const [r, c] = stack.pop();for (const [dr, dc] of [[1, 0], [-1, 0], [0, 1], [0, -1]]) {const nr = r + dr;const nc = c + dc;if (nr < 0 || nr >= grid.length || nc < 0 || nc >= grid[0].length) continue;if (grid[nr][nc] !== oldColor) continue;grid[nr][nc] = newColor;stack.push([nr, nc]);}}return grid;}
How fast it is
| Time | O(rows * cols) |
|---|---|
| Extra memory | O(rows * cols) |
Each cell is painted and pushed at most once, and each pop checks 4 neighbors. In the worst case the whole grid is one region and sits on the stack.
- Best case
O(1)oldColoralready equalsnewColor, or the start cell's four neighbors all have other colors. Only a few cells are checked. - Average case
O(k)kis the number of cells in the region. Each is pushed once and checks 4 neighbors, whatever the grid size. - Worst case
O(rows × cols)The whole grid has one color, so every cell is in the region.
Common mistakes
- Forgetting the
oldColor === newColorcheck. Painted cells still match, so the fill runs forever. - Checking
grid[nr][nc]before the bounds check. Readinggrid[-1]givesundefined, and reading a column of it crashes. - Painting a cell when it is popped instead of when it is pushed. The same cell can be pushed by several neighbors.
- Using recursion on a huge region. A 1000 by 1000 canvas can overflow the call stack; an explicit
stackarray does not.
When to use it
Look for these clues in a problem:
- The input is a grid of characters or numbers, like
["..#", ".##"]. - The question asks about regions, shapes, islands, or areas of touching cells.
- Cells connect up, down, left, and right (sometimes diagonally too).
- You need to paint, count, measure, or remove a connected region.
Check yourself
Answer each one in your head first, then open it.
Why paint a cell when it is pushed, not when it is popped?
If you wait until the pop, two neighbors can both see the cell still has oldColor and both push it. Painting at the push makes the second neighbor see newColor and skip it.
How many times can one cell be pushed?
At most once. It is pushed only while it has oldColor, and it is painted in the same step, so it never matches again.
Does changing pop to taking from the front change which cells are painted?
No. It changes the order to rings around the start, but the same connected cells are painted either way.
In the trace, why is (1,2) popped before (0,1), even though (0,1) was found first?
Both were pushed by (1,1), with (1,2) pushed last. A stack hands back the newest item first, so the fill goes deep to the right before coming back.
Practice problems
Stuck on one? Tuto is in the playground too, and gives you hints, never the answer.
0 of 7 solved. Solve one to earn the bronze medal, half for silver, and all of them for gold.
Start practicingGo further
You don't need these to start practicing. Open one when you want more.
Why it works
The reasoning that makes it always right
The fill keeps one rule: every painted cell belongs to the region, meaning it had oldColor and connects to the start through cells that had oldColor. The start cell fits. A neighbor is painted only if it is next to a painted cell and still has oldColor, so it fits too.
It also misses nothing. Every painted cell is pushed once and later popped, and the pop checks all four of its neighbors. So when the stack is empty, no painted cell has an unpainted oldColor neighbor. Any region cell left unpainted would have a path from the start, and somewhere on that path an unpainted region cell would sit next to a painted one, which can't happen.
It always ends, because each push changes a cell from oldColor to newColor, and that can happen at most once per cell when the colors differ. The early return for oldColor === newColor is what makes that true: without it, a painted cell would still match and could be pushed forever.
Going deeper
3 more ideas
Why painting works as the visited mark
A cell is only pushed when it still has oldColor, and it is painted at the same moment. After that it no longer equals oldColor, so no neighbor can push it again. That's why the early return for oldColor === newColor matters: without it, painted cells would still look unvisited and the loop would never end.
How long it takes
Each cell is pushed at most once and checks 4 neighbors, so the work is about 4 * rows * cols, which is O(rows * cols). The stack can hold up to every cell in the region, so the extra memory is also O(rows * cols) in the worst case.
Counting regions and other variations
To count islands, loop over every cell; each time you find an unpainted land cell, add one to a counter and flood fill from it. For 8-way connection, add the four diagonal directions. If you must not change the input, keep a separate seen grid instead of painting.
Work it by hand
Every step in a table, with why
Fill the region of 5s that contains the cell at row 1, column 1 with the color 8. Two other 5s touch the region only at a corner.
grid = [[5, 5, 2, 5], [2, 5, 5, 2], [5, 2, 5, 5]], row = 1, col = 1, newColor = 8
| Step | Popped [r, c] | Painted now | stack after | What happens | Why |
|---|---|---|---|---|---|
| 1 | - | (1,1) | (1,1) | oldColor = 5 | The start cell is 5, which differs from 8, so paint it and push it. |
| 2 | (1,1) | (0,1), (1,2) | (0,1), (1,2) | Two neighbors match | Down (2,1) is 2 and left (1,0) is 2. Up (0,1) and right (1,2) are 5, so both are painted and pushed. |
| 3 | (1,2) | (2,2) | (0,1), (2,2) | Go deeper to the right | pop takes the newest cell, (1,2). Only its down neighbor (2,2) is still 5; (1,1) is already 8. |
| 4 | (2,2) | (2,3) | (0,1), (2,3) | Keep going | Down is outside the grid. Up is painted. Right (2,3) is 5. Left (2,1) is 2. |
| 5 | (2,3) | none | (0,1) | Dead end | Down and right are outside the grid, up (1,3) is 2, and left (2,2) is painted. |
| 6 | (0,1) | (0,0) | (0,0) | Back to the older cell | Down (1,1) is painted, up is outside, right (0,2) is 2, and left (0,0) is 5. |
| 7 | (0,0) | none | empty | Stop | Its only in-grid neighbors are (1,0), which is 2, and (0,1), which is painted. The stack is empty. |
The grid becomes [[8, 8, 2, 5], [2, 8, 8, 2], [5, 2, 8, 8]]: 6 cells painted, each pushed and popped once. The 5s at (0,3) and (2,0) stay, because they only touch the region at a corner, and corners don't count.
Compared with other ways
When to pick something else
| Approach | Time | Extra memory | Choose it when |
|---|---|---|---|
| Flood fill | O(rows × cols) | O(rows × cols) | You need to paint, measure, or remove one connected region of a grid. |
| Recursive flood fill | O(rows × cols) | O(rows × cols) | The grid is small and you want the shortest code. A large region can overflow the call stack. |
| Union-find over all cells | O(rows × cols · α(n)) | O(rows × cols) | You need to label every region at once, or cells keep changing and regions keep merging over time. |
Extra memory: O(rows × cols) in the worst case, for the stack. It is often much less, because cells leave the stack as others join it.
How the steps grow
- Flood fill, Recursive flood fill, Union-find over all cells
O(rows × cols)
| Approach | n = 10 | n = 1,000 | n = 1,000,000 |
|---|---|---|---|
| Flood fill | 100 | 1 million | 1 trillion |
| Recursive flood fill | 100 | 1 million | 1 trillion |
| Union-find over all cells | 100 | 1 million | 1 trillion |
Inputs that need care
5 tricky inputs
newColorequals the start's color- Return the grid unchanged at once. Without this check the start cell would be pushed again by its own neighbor, and the loop would never end.
- A 1 by 1 grid
- The start is painted and pushed. Its pop finds all four neighbors outside the grid, and the fill ends after one cell.
- Cells of the same color that touch only at corners
- They are not filled, as
(0,3)and(2,0)in the trace show. If the problem says diagonal cells connect, add the four diagonal directions. - An empty grid, or rows of different lengths
- The code reads
grid[row][col]andgrid[0].length, so it expects at least one cell and equal-length rows. Check the input first if that isn't promised. - A 4000 by 4000 canvas filled with one color
- That is 16 million cells, and each
[r, c]pair is a small array. Store cells as one number,r * cols + c, in a typed array to save memory and time.
Variations and follow-ups
Twists interviewers add
Breadth-first fill
Take cells from the front with a head index instead of pop. It paints exactly the same cells, but in rings around the start, which also lets you record each cell's distance from the click.
Fill with a tolerance
Image editors often fill colors that are only close to oldColor. Then newColor might also count as close, so painting no longer works as the visited mark. Keep a separate seen grid of booleans.
Scanline fill
Paint a whole horizontal run of matching cells at once, then push only one cell for each new run found in the rows above and below. It pushes far fewer cells, which is why paint programs often use it.
Counting and measuring regions
Loop over every cell. Each time you find one that isn't painted yet, add one to a counter and fill from it, counting the cells it paints. That gives the number of islands and each island's area.
In an interview
Spot it, explain it, answer follow-ups
- Spot it from a grid plus words like region, island, area, paint, or connected cells. The grid is a graph whose edges are the four directions.
- Explain the mark out loud: "painting is my visited mark, so I paint when I push, and I return early if the colors are the same".
- Test three tiny cases by hand: same color, a 1 by 1 grid, and two same-colored cells that touch only at a corner.
- Expect "what if you can't change the input?": keep a separate
seengrid. And "what about very large images?": use an explicit stack, not recursion, and store cells as single numbers.
Where frontend code uses it
The same idea in real UI code
- The paint bucket tool in drawing apps and canvas editors fills a region of the
ImageDatapixel array this way. - The magic wand selection tool in image editors selects touching pixels of a similar color with the same spread.
- Grid games built in the browser, such as Minesweeper, reveal a whole empty area with a flood fill when you click an empty cell.