Hi👋SpeedAlgo • Deliberate Practice & Cognitive Ergonomics for Software Engineers

An interactive algorithm mastery and technical interview preparation platform published by Hi👋WebEnterprise. Built for senior and staff software engineers preparing for rigorous coding screens at top tech companies (FAANG/MAMAA).

13 Core Algorithmic Patterns & 225 Practice Problems

  • 1. Two Pointers (16 Paradigms, 43 Problems): Opposite ends, sorted pair sums, palindrome verification, trapping rain water, 3Sum, container with most water — includes the Sliding Window and Fast & Slow Pointers paradigms (contiguous subarray invariants, longest substrings, Floyd's cycle detection, linked list middle).
  • 2. Binary Search (9 Paradigms, 14 Problems): Monotonic predicate partitioning (feasible(x)), boundary search, rotated sorted arrays, square roots, minimum ship capacity, median of two sorted arrays, matrix median on value range.
  • 3. Bit Manipulation (5 Paradigms, 11 Problems): Counting set bits, XOR cancellation, bit reversal, bitmask pairing, maximum XOR with a bitwise trie.
  • 4. Math & Geometry (8 Paradigms, 14 Problems): Sieve of Eratosthenes, integer/roman conversions, modular arithmetic, geometric simulation.
  • 5. Tree/Graph Depth-First Search (15 Paradigms, 24 Problems): Path sums, lowest common ancestor, tree diameter, validating BSTs, tree DP (House Robber III, Binary Tree Cameras).
  • 6. Tree/Graph Breadth-First Search (5 Paradigms, 12 Problems): Level-order traversals, shortest path in unweighted graphs, zig-zag traversals, rotting oranges, word ladders.
  • 7. Graphs (12 Paradigms, 22 Problems): Topological sort, cycle detection, Dijkstra's algorithm, bipartite graph validation, network delay.
  • 8. Backtracking (6 Paradigms, 14 Problems): Subsets, permutations, combinations, constraint satisfaction, pruning, N-Queens, Sudoku solver.
  • 9. Dynamic Programming (13 Paradigms, 28 Problems): Memoization vs tabulation, 0/1 knapsack, unbounded knapsack, coin change, edit distance, longest common subsequence.
  • 10. Heap / Priority Queue (9 Paradigms, 11 Problems): Running medians, top-k elements, interval scheduling, IPO, k-way merges.
  • 11. Advanced Data Structures (7 Paradigms, 17 Problems): Trie (prefix tree), Union-Find (Disjoint Set Union), LRU Cache, LFU Cache, range-sum queries with updates.
  • 12. Stack (5 Paradigms, 9 Problems): Next greater element, largest rectangle in histogram, remove k digits, decode string, min stack.
  • 13. Intervals & Greedy (2 Paradigms, 6 Problems): Merge intervals, interval scheduling, meeting rooms by sweep line, jump game.

4-Stage Deliberate Practice Framework

  1. Stage 1 (Compare & Learn): Read the pattern's mental model, loop invariant and Big-O proof, compare the pattern's template with the problem's solution side by side, and use the step-by-step debugger. The solution can also be shown in C#, Java, TypeScript, C++, Go or Rust for almost every problem.
  2. Stage 2 (Active Recall): Rebuild the solution from memory in a code editor, with an invariant checklist and optional progressive hints, and check it against a judged test suite.
  3. Stage 3 (AI Interview Coach, coming soon): A mock-interview stage still in development; for now, students go straight from Stage 2 to Stage 4.
  4. Stage 4 (Solve on Your Own): Solve a new problem on your own against a 15-minute timer; your code is graded against a test suite. Python and TypeScript run in your browser and C# runs on our server (design problems grade in Python and TypeScript).

Equipped with a spaced-repetition review hub on a fixed 7-step schedule (1 day up to 90 days), a two-pane Studio workspace, and study notes.

Pricing, Access & Commercial Terms

  • Core Curriculum: 100% Free. No credit card required.
  • Coins: New accounts get 40 free coins once, the daily claim adds +20 coins once a day, and inviting a friend earns you +25 coins when they complete their first practice step. These are the only ways to earn coins.
  • AI Coach: Each AI coach question costs 1-5 coins, priced by the server according to its size.
  • BYOK (Bring Your Own Key): AI coaching is free with your own Google Gemini, OpenAI or OpenRouter API key (BYOK).
  • Extras: Coins also unlock extras for 24 hours: a Recall hint level (1 coin), a Solve solution reveal (2), a LeetCode problem card's solutions (1) and a pattern's extra canonical problems (1).
  • Payments & Refunds: Nothing on the site is sold for money: coins can only be earned, never bought. There are no subscriptions or charges, so there is nothing to refund.
  • Platform Operator: Hi👋WebEnterprise. Support & policies at hispeedalgo.com.
Skip to main content
Hi👋SpeedAlgo

Invariant-First Algorithmic Mastery

Arena
238Items
Theory Context•Math & Geometry

Builds on Converging Pointers, which you haven’t finished yet.

P1MediumMental model~7 min speedrun

Spiral Matrix Traversal (Layer-by-Layer Boundary Shrinking)

The problem

Spiral Matrix (LC 54)(opens leetcode.com)

You're given an m x n matrix. Read off every cell in spiral order and return that as one flat list.

Picture peeling the matrix like an onion: sweep across the still-unvisited top row heading right, then drop down the still-unvisited rightmost column, then sweep back across the still-unvisited bottom row heading left, then climb the still-unvisited leftmost column -- shrinking the unvisited region on each pass until every cell has been read.

Example 1
Input:matrix = [[1,2,3],[4,5,6],[7,8,9]]
Output:[1,2,3,6,9,8,7,4,5]
Top row left-to-right (1,2,3), right column top-to-bottom (6,9), bottom row right-to-left (8,7), then the lone remaining middle cell (4,5).
123456789

Spiral Matrix

grid (R x C) ➔ top/bottom/left/right ➔ inward spiral
Spot it when: You must walk a matrix ring by ring: spiral order, filling a spiral, rotating layers.
Spiral Matrix
Python
1def spiral_order(matrix: list[list[int]]) -> list[int]:
2 """Spiral Matrix: read the matrix ring by ring with four boundaries.
3 Time: O(R * C) | Space: O(1) auxiliary
4 """
5 if not matrix or not matrix[0]:
6 return []
7 
8 top, bottom = 0, len(matrix) - 1
9 left, right = 0, len(matrix[0]) - 1
10 result: list[int] = []
11 
12 while top <= bottom and left <= right:
13 # 1. Sweep Top (Left -> Right)
14 for c in range(left, right + 1):
15 result.append(matrix[top][c])
16 top += 1
17 
18 # 2. Sweep Right (Top -> Bottom)
19 for r in range(top, bottom + 1):
20 result.append(matrix[r][right])
21 right -= 1
22 
23 # 3. Sweep Bottom (Right -> Left) with Guard!
24 if top <= bottom:
25 for c in range(right, left - 1, -1):
26 result.append(matrix[bottom][c])
27 bottom -= 1
28 
29 # 4. Sweep Left (Bottom -> Top) with Guard!
30 if left <= right:
31 for r in range(bottom, top - 1, -1):
32 result.append(matrix[r][left])
33 left += 1
34 
35 return result
Invariant: top, bottom, left and right always bound exactly the cells not yet added to result.

Walk the matrix one ring at a time with four boundaries, top, bottom, left and right, that close in from all four sides like converging pointers, and move each boundary inward right after its side is read. Each boundary marks the first row or column not yet read on its side, so after the four sweeps the unread cells form a smaller rectangle and the same sweeps repeat on it. The trap is the last, thin ring: after the top and right sweeps there may be no row or no column left, so guard the bottom sweep with if top <= bottom and the left sweep with if left <= right, or a single row such as [[1,2,3]] returns [1,2,3,2,1].

See it run

Spiral Matrix: peeling a 3 x 4 matrix with four boundaries

top 0 · bottom 2 · left 0 · right 3 · result []

top 1 · bottom 2 · left 0 · right 3 · result [1, 2, 3, 4]

top 1 · bottom 2 · left 0 · right 2 · result [1, 2, 3, 4, 8, 12]

top 1 · bottom 1 · left 0 · right 2 · result [1, 2, 3, 4, 8, 12, 11, 10, 9]

top 1 · bottom 1 · left 1 · right 2 · result [1, 2, 3, 4, 8, 12, 11, 10, 9, 5]

top 2 · bottom 1 · left 1 · right 2 · result [1, 2, 3, 4, 8, 12, 11, 10, 9, 5, 6, 7]

top 2 · bottom 1 · left 1 · right 1 · result [1, 2, 3, 4, 8, 12, 11, 10, 9, 5, 6, 7]

top 2 · bottom 1 · left 1 · right 1 · result [1, 2, 3, 4, 8, 12, 11, 10, 9, 5, 6, 7]

top 2 · bottom 1 · left 2 · right 1 · result [1, 2, 3, 4, 8, 12, 11, 10, 9, 5, 6, 7]

top 2 · bottom 1 · left 2 · right 1 · answer [1, 2, 3, 4, 8, 12, 11, 10, 9, 5, 6, 7]

Each cell shows its value until it is read. A blank cell is already in result.

  • read a cell this sweep adds to result
  • again a cell the skipped sweep would add a second time

The matrix has 3 rows and 4 columns, so top = 0, bottom = 2, left = 0 and right = 3. A cell that is read becomes blank.

Top sweep: it reads [1, 2, 3, 4]. Then top becomes 1.

Right sweep: it reads [8, 12]. Then right becomes 2.

Bottom sweep: it reads [11, 10, 9]. Then bottom becomes 1.

Left sweep: it reads [5]. Then left becomes 1.

Top sweep: it reads [6, 7]. Then top becomes 2.

Right sweep: the range has no cells, so nothing is added. Then right becomes 1.

Bottom guard: top <= bottom (2 <= 1) is false, so this sweep is skipped. Without the guard, it would add [6] to result a second time.

Left sweep: the range has no cells, so nothing is added. Then left becomes 2.

The loop stops because top = 2, bottom = 1, left = 2 and right = 1 no longer enclose any cell. result holds all 12 values, each once.

row 0
col 01
col 12
col 23
col 34
row 1
5
6
7
8
row 2
9
10
11
12
1 / 10
Gotchas1 of 4

Gotcha 1 of 4

  1. 1
    The trap

    Gotcha 1: Leaving out the if top <= bottom guard

    Mistake
    for c in range(right, left - 1, -1):
        result.append(matrix[bottom][c])
    bottom -= 1
    Fix
    if top <= bottom:
        for c in range(right, left - 1, -1):
            result.append(matrix[bottom][c])
        bottom -= 1

    What happens: After the top sweep and the right sweep, the rows may be used up. For [[1,2,3]], the top sweep reads row 0 and sets top to 1, while bottom is 0. Without the guard, the bottom sweep reads row 0 again, backwards. The left sweep needs if left <= right for the same reason.

    Example: matrix = [[1, 2, 3]]
    Mistake: returns [1, 2, 3, 2, 1]Fix: returns [1, 2, 3]
  2. 2

    Gotcha 2: Forgetting top += 1 after the top sweep

    Mistake
    for c in range(left, right + 1):
        result.append(matrix[top][c])
    Fix
    for c in range(left, right + 1):
        result.append(matrix[top][c])
    top += 1

    What happens: top marks the first row that is not read yet. The right sweep starts at row top. Without top += 1, the right sweep reads the top-right corner a second time. Later sweeps then read other cells twice as well.

    Example: matrix = [[1, 2], [3, 4]]
    Mistake: returns [1, 2, 2, 4, 3, 1]Fix: returns [1, 2, 4, 3]
  3. 3

    Gotcha 3: Writing range(right, left, -1) in the bottom sweep

    Mistake
    for c in range(right, left, -1):
    Fix
    for c in range(right, left - 1, -1):

    What happens: range stops before its end value. range(right, left, -1) stops before column left, so the bottom sweep skips it. For [[1,2],[3,4]], the value 3 is never read. The left sweep has the same kind of bound, range(bottom, top - 1, -1).

    Example: matrix = [[1, 2], [3, 4]]
    Mistake: returns [1, 2, 4]Fix: returns [1, 2, 4, 3]
  4. 4

    Gotcha 4: Looping range(right - left + 1) when rotating a ring

    Mistake
    for i in range(right - left + 1):
    Fix
    for i in range(right - left):

    What happens: In Rotate Image (LC 48), each ring can be turned in place. For each i in range(right - left), four cells move one side along, through one saved value. A side has right - left + 1 cells, but its last cell is the first cell of the next side. With range(right - left + 1), each corner moves twice.

    Example: matrix = [[1, 2], [3, 4]]
    Mistake: returns [[4, 3], [2, 1]]Fix: returns [[3, 1], [4, 2]]
Not this one if…
  • The grid is read in plain row order and every value moves forward by the same number of places, as in Shift 2D Grid (LC 1260), not ring by ring. See Three-Reversals Algorithm

Deep Dive

What the four boundaries mean

top, bottom, left and right mark the rectangle of cells that are not in result yet. Cell matrix[r][c] is unread exactly when top <= r <= bottom and left <= c <= right. This is the card's invariant.

Each sweep reads one side of that rectangle, and then moves the boundary on that side inward by 1. The side that was just read is no longer inside the rectangle. So the rectangle always shrinks by exactly the cells that were read.

In Spiral Matrix (LC 54) with a 3 by 4 matrix, the first four sweeps read 1, 2, 3, 4, then 8, 12, then 11, 10, 9, then 5. The rectangle is now one row and two columns: top = 1, bottom = 1, left = 1, right = 2. The second ring reads 6, 7. The final result is [1, 2, 3, 4, 8, 12, 11, 10, 9, 5, 6, 7].

Why each cell is read exactly once

The four sweeps of one ring read the top row, the right column, the bottom row and the left column of the rectangle. The top row is read first, and top += 1 removes it from the rectangle. So the right sweep starts at top, which is the row below the top-right corner. That corner is not read twice.

The same order removes the other corners. right -= 1 removes the right column before the bottom sweep, so the bottom sweep starts at the cell left of the bottom-right corner. bottom -= 1 removes the bottom row before the left sweep, so the left sweep stops above the bottom-left corner.

Every cell is in one ring, and every ring is read by one pass of the while loop. So result gets R * C values, one per cell, and no cell appears twice. The number of rings is the smaller side divided by 2, rounded up. A 3 by 4 matrix has 2 rings, a 5 by 5 matrix has 3 rings, and a matrix with one row has 1 ring.

Why the last ring needs two guards

A full ring has at least two rows and two columns. The last ring can be one row, one column or one cell. In that case the top sweep and the right sweep can already use up the rows or the columns.

The 3 by 4 example shows this. In the second ring, the top sweep reads 6, 7 and sets top = 2, while bottom = 1. The test top <= bottom is false, so the bottom sweep is skipped. Without the guard, it runs range(right, left - 1, -1) on row 1 and adds 6 a second time. The result then ends with ..., 5, 6, 7, 6.

The single row [[1, 2, 3]] gives [1, 2, 3, 2, 1] without the first guard. The single column [[1], [2], [3]] gives [1, 2, 3, 2] without the second guard, left <= right. With both guards, the results are [1, 2, 3] for both inputs.

The rule is: after a sweep changes a boundary, ask whether the next side still has cells. The top sweep and the right sweep do not need a guard, because the while condition already guarantees one row and one column at the start of each ring.

The cost, and the same boundaries in related problems

Every cell is read once, so the time is O(R * C). The only extra memory is four integers, so the auxiliary space is O(1). The output list holds R * C values, but it is the answer and does not count as extra memory.

A different method keeps a visited grid and a direction, and turns right when the next cell is outside the matrix or already visited. It produces the same order, but it needs O(R * C) extra memory for visited.

The same four boundaries solve Spiral Matrix II (LC 59), which writes the numbers 1 to n * n into an empty grid in spiral order: replace result.append(...) with an assignment from a counter. Spiral Matrix IV (LC 2326) does the same with the values of a linked list. Rotate Image (LC 48) and Cyclically Rotating a Grid (LC 1914) treat each ring as a closed loop of cells, and move the values along that loop.

60-Second Pitch

"I read the matrix ring by ring with four boundaries: top, bottom, left and right. They enclose exactly the cells that are not in the result yet. Each pass reads the top row, the right column, the bottom row and the left column. After each sweep, I move the boundary on that side inward by 1. The last ring can be one row or one column. So I check top <= bottom before the bottom sweep and left <= right before the left sweep. Without them, a cell would be read twice. Every cell is read once, so the time is O(R * C). I keep four integers, so the extra space is O(1)."

Common Questions

Why are only the bottom sweep and the left sweep guarded?

The while condition guarantees at least one row and one column at the start of the pass. So the top sweep and the right sweep always have cells to read. The top sweep then removes a row and the right sweep removes a column. After that, the rectangle can be empty in one direction, so the bottom sweep and the left sweep each need a check.

What is the time and space complexity of spiral_order?

O(R * C) time, because every cell is added to result once and the boundary updates are constant work. The auxiliary space is O(1): four integers. The output list holds R * C values, and it is the answer.

How does a visited grid compare with the boundary method?

The visited method walks one cell at a time in a direction and turns when the next cell is outside the matrix or already visited. It gives the same order and the same O(R * C) time. It needs an extra grid of R * C flags. The boundary method needs no extra grid.

How do you change the code to fill a grid, as in Spiral Matrix II (LC 59)?

Create an n by n grid of zeros. Use the same four boundaries and the same four sweeps. Replace each result.append(matrix[r][c]) with grid[r][c] = counter, and then add 1 to counter. The guards stay, although for a square grid the only thin ring is the center cell of an odd n.

What changes for Rotate Image (LC 48)?

The task does not read the cells in order. It turns each ring by a quarter. For each i in range(right - left), four cells swap positions through one saved value. A side has right - left + 1 cells, but its last cell starts the next side, so the loop runs right - left times.

SWE Reasoning

Spot it

  • The output order or the update order is a spiral: inward, ring by ring, starting at a corner.
  • The task writes values into a grid in spiral order, as in Spiral Matrix II (LC 59), or reads them in spiral order, as in Spiral Matrix (LC 54).
  • The task moves each ring of a grid as a loop, as in Rotate Image (LC 48) or Cyclically Rotating a Grid (LC 1914).
  • The matrix may have one row, one column or one cell left in the middle, and the code must not read it twice.
  • You are asked for O(1) extra memory, so a visited grid is not allowed.

The invariant, step by step

  1. At the start of a loop pass, top, bottom, left and right enclose exactly the cells not in result. The while condition says this rectangle has at least one row and one column.
  2. The top sweep reads row top from left to right. Then top += 1 removes that row from the rectangle.
  3. The right sweep reads column right from the new top to bottom. Then right -= 1 removes that column.
  4. Before the bottom sweep, if top <= bottom asks whether a row is left. If it is, the sweep reads row bottom from right to left, and bottom -= 1 removes it.
  5. Before the left sweep, if left <= right asks whether a column is left. If it is, the sweep reads column left from bottom to top, and left += 1 removes it.
  6. The four boundaries again enclose exactly the unread cells, so the invariant holds for the next pass. The loop ends when the rectangle has no cell, and result has every cell once.

Traps

  • No top <= bottom guard. On a thin last ring, the bottom sweep reads a row that was already read. For [[1, 2, 3]], the result is [1, 2, 3, 2, 1].
  • No left <= right guard. On a single column, the left sweep reads a column that was already read. For [[1], [2], [3]] without this guard, the result is [1, 2, 3, 2].
  • A boundary is not moved after its sweep. The next sweep then starts on a cell that is already in result, so the corner is read twice.
  • A range that stops one cell early. range(right, left, -1) stops before column left, so the bottom sweep skips the bottom-left corner. Use range(right, left - 1, -1).
  • An empty matrix. matrix[0] raises IndexError for []. The first line, if not matrix or not matrix[0], returns [] instead.

Practice

Canonical LeetCode:Spiral Matrix (LC 54)Spiral Matrix II (LC 59)Rotate Image (LC 48)Spiral Matrix IV (LC 2326)Cyclically Rotating a Grid (LC 1914)
Theory Context•Math & Geometry

Builds on Converging Pointers, which you haven’t finished yet.

P1MediumMental model~7 min speedrun

Spiral Matrix Traversal (Layer-by-Layer Boundary Shrinking)

The problem

Spiral Matrix (LC 54)(opens leetcode.com)

You're given an m x n matrix. Read off every cell in spiral order and return that as one flat list.

Picture peeling the matrix like an onion: sweep across the still-unvisited top row heading right, then drop down the still-unvisited rightmost column, then sweep back across the still-unvisited bottom row heading left, then climb the still-unvisited leftmost column -- shrinking the unvisited region on each pass until every cell has been read.

Example 1
Input:matrix = [[1,2,3],[4,5,6],[7,8,9]]
Output:[1,2,3,6,9,8,7,4,5]
Top row left-to-right (1,2,3), right column top-to-bottom (6,9), bottom row right-to-left (8,7), then the lone remaining middle cell (4,5).
123456789

Spiral Matrix

grid (R x C) ➔ top/bottom/left/right ➔ inward spiral
Spot it when: You must walk a matrix ring by ring: spiral order, filling a spiral, rotating layers.
Spiral Matrix
Python
1def spiral_order(matrix: list[list[int]]) -> list[int]:
2 """Spiral Matrix: read the matrix ring by ring with four boundaries.
3 Time: O(R * C) | Space: O(1) auxiliary
4 """
5 if not matrix or not matrix[0]:
6 return []
7 
8 top, bottom = 0, len(matrix) - 1
9 left, right = 0, len(matrix[0]) - 1
10 result: list[int] = []
11 
12 while top <= bottom and left <= right:
13 # 1. Sweep Top (Left -> Right)
14 for c in range(left, right + 1):
15 result.append(matrix[top][c])
16 top += 1
17 
18 # 2. Sweep Right (Top -> Bottom)
19 for r in range(top, bottom + 1):
20 result.append(matrix[r][right])
21 right -= 1
22 
23 # 3. Sweep Bottom (Right -> Left) with Guard!
24 if top <= bottom:
25 for c in range(right, left - 1, -1):
26 result.append(matrix[bottom][c])
27 bottom -= 1
28 
29 # 4. Sweep Left (Bottom -> Top) with Guard!
30 if left <= right:
31 for r in range(bottom, top - 1, -1):
32 result.append(matrix[r][left])
33 left += 1
34 
35 return result
Invariant: top, bottom, left and right always bound exactly the cells not yet added to result.

Walk the matrix one ring at a time with four boundaries, top, bottom, left and right, that close in from all four sides like converging pointers, and move each boundary inward right after its side is read. Each boundary marks the first row or column not yet read on its side, so after the four sweeps the unread cells form a smaller rectangle and the same sweeps repeat on it. The trap is the last, thin ring: after the top and right sweeps there may be no row or no column left, so guard the bottom sweep with if top <= bottom and the left sweep with if left <= right, or a single row such as [[1,2,3]] returns [1,2,3,2,1].

See it run

Spiral Matrix: peeling a 3 x 4 matrix with four boundaries

top 0 · bottom 2 · left 0 · right 3 · result []

top 1 · bottom 2 · left 0 · right 3 · result [1, 2, 3, 4]

top 1 · bottom 2 · left 0 · right 2 · result [1, 2, 3, 4, 8, 12]

top 1 · bottom 1 · left 0 · right 2 · result [1, 2, 3, 4, 8, 12, 11, 10, 9]

top 1 · bottom 1 · left 1 · right 2 · result [1, 2, 3, 4, 8, 12, 11, 10, 9, 5]

top 2 · bottom 1 · left 1 · right 2 · result [1, 2, 3, 4, 8, 12, 11, 10, 9, 5, 6, 7]

top 2 · bottom 1 · left 1 · right 1 · result [1, 2, 3, 4, 8, 12, 11, 10, 9, 5, 6, 7]

top 2 · bottom 1 · left 1 · right 1 · result [1, 2, 3, 4, 8, 12, 11, 10, 9, 5, 6, 7]

top 2 · bottom 1 · left 2 · right 1 · result [1, 2, 3, 4, 8, 12, 11, 10, 9, 5, 6, 7]

top 2 · bottom 1 · left 2 · right 1 · answer [1, 2, 3, 4, 8, 12, 11, 10, 9, 5, 6, 7]

Each cell shows its value until it is read. A blank cell is already in result.

  • read a cell this sweep adds to result
  • again a cell the skipped sweep would add a second time

The matrix has 3 rows and 4 columns, so top = 0, bottom = 2, left = 0 and right = 3. A cell that is read becomes blank.

Top sweep: it reads [1, 2, 3, 4]. Then top becomes 1.

Right sweep: it reads [8, 12]. Then right becomes 2.

Bottom sweep: it reads [11, 10, 9]. Then bottom becomes 1.

Left sweep: it reads [5]. Then left becomes 1.

Top sweep: it reads [6, 7]. Then top becomes 2.

Right sweep: the range has no cells, so nothing is added. Then right becomes 1.

Bottom guard: top <= bottom (2 <= 1) is false, so this sweep is skipped. Without the guard, it would add [6] to result a second time.

Left sweep: the range has no cells, so nothing is added. Then left becomes 2.

The loop stops because top = 2, bottom = 1, left = 2 and right = 1 no longer enclose any cell. result holds all 12 values, each once.

row 0
col 01
col 12
col 23
col 34
row 1
5
6
7
8
row 2
9
10
11
12
1 / 10
Gotchas1 of 4

Gotcha 1 of 4

  1. 1
    The trap

    Gotcha 1: Leaving out the if top <= bottom guard

    Mistake
    for c in range(right, left - 1, -1):
        result.append(matrix[bottom][c])
    bottom -= 1
    Fix
    if top <= bottom:
        for c in range(right, left - 1, -1):
            result.append(matrix[bottom][c])
        bottom -= 1

    What happens: After the top sweep and the right sweep, the rows may be used up. For [[1,2,3]], the top sweep reads row 0 and sets top to 1, while bottom is 0. Without the guard, the bottom sweep reads row 0 again, backwards. The left sweep needs if left <= right for the same reason.

    Example: matrix = [[1, 2, 3]]
    Mistake: returns [1, 2, 3, 2, 1]Fix: returns [1, 2, 3]
  2. 2

    Gotcha 2: Forgetting top += 1 after the top sweep

    Mistake
    for c in range(left, right + 1):
        result.append(matrix[top][c])
    Fix
    for c in range(left, right + 1):
        result.append(matrix[top][c])
    top += 1

    What happens: top marks the first row that is not read yet. The right sweep starts at row top. Without top += 1, the right sweep reads the top-right corner a second time. Later sweeps then read other cells twice as well.

    Example: matrix = [[1, 2], [3, 4]]
    Mistake: returns [1, 2, 2, 4, 3, 1]Fix: returns [1, 2, 4, 3]
  3. 3

    Gotcha 3: Writing range(right, left, -1) in the bottom sweep

    Mistake
    for c in range(right, left, -1):
    Fix
    for c in range(right, left - 1, -1):

    What happens: range stops before its end value. range(right, left, -1) stops before column left, so the bottom sweep skips it. For [[1,2],[3,4]], the value 3 is never read. The left sweep has the same kind of bound, range(bottom, top - 1, -1).

    Example: matrix = [[1, 2], [3, 4]]
    Mistake: returns [1, 2, 4]Fix: returns [1, 2, 4, 3]
  4. 4

    Gotcha 4: Looping range(right - left + 1) when rotating a ring

    Mistake
    for i in range(right - left + 1):
    Fix
    for i in range(right - left):

    What happens: In Rotate Image (LC 48), each ring can be turned in place. For each i in range(right - left), four cells move one side along, through one saved value. A side has right - left + 1 cells, but its last cell is the first cell of the next side. With range(right - left + 1), each corner moves twice.

    Example: matrix = [[1, 2], [3, 4]]
    Mistake: returns [[4, 3], [2, 1]]Fix: returns [[3, 1], [4, 2]]
Not this one if…
  • The grid is read in plain row order and every value moves forward by the same number of places, as in Shift 2D Grid (LC 1260), not ring by ring. See Three-Reversals Algorithm

Deep Dive

What the four boundaries mean

top, bottom, left and right mark the rectangle of cells that are not in result yet. Cell matrix[r][c] is unread exactly when top <= r <= bottom and left <= c <= right. This is the card's invariant.

Each sweep reads one side of that rectangle, and then moves the boundary on that side inward by 1. The side that was just read is no longer inside the rectangle. So the rectangle always shrinks by exactly the cells that were read.

In Spiral Matrix (LC 54) with a 3 by 4 matrix, the first four sweeps read 1, 2, 3, 4, then 8, 12, then 11, 10, 9, then 5. The rectangle is now one row and two columns: top = 1, bottom = 1, left = 1, right = 2. The second ring reads 6, 7. The final result is [1, 2, 3, 4, 8, 12, 11, 10, 9, 5, 6, 7].

Why each cell is read exactly once

The four sweeps of one ring read the top row, the right column, the bottom row and the left column of the rectangle. The top row is read first, and top += 1 removes it from the rectangle. So the right sweep starts at top, which is the row below the top-right corner. That corner is not read twice.

The same order removes the other corners. right -= 1 removes the right column before the bottom sweep, so the bottom sweep starts at the cell left of the bottom-right corner. bottom -= 1 removes the bottom row before the left sweep, so the left sweep stops above the bottom-left corner.

Every cell is in one ring, and every ring is read by one pass of the while loop. So result gets R * C values, one per cell, and no cell appears twice. The number of rings is the smaller side divided by 2, rounded up. A 3 by 4 matrix has 2 rings, a 5 by 5 matrix has 3 rings, and a matrix with one row has 1 ring.

Why the last ring needs two guards

A full ring has at least two rows and two columns. The last ring can be one row, one column or one cell. In that case the top sweep and the right sweep can already use up the rows or the columns.

The 3 by 4 example shows this. In the second ring, the top sweep reads 6, 7 and sets top = 2, while bottom = 1. The test top <= bottom is false, so the bottom sweep is skipped. Without the guard, it runs range(right, left - 1, -1) on row 1 and adds 6 a second time. The result then ends with ..., 5, 6, 7, 6.

The single row [[1, 2, 3]] gives [1, 2, 3, 2, 1] without the first guard. The single column [[1], [2], [3]] gives [1, 2, 3, 2] without the second guard, left <= right. With both guards, the results are [1, 2, 3] for both inputs.

The rule is: after a sweep changes a boundary, ask whether the next side still has cells. The top sweep and the right sweep do not need a guard, because the while condition already guarantees one row and one column at the start of each ring.

The cost, and the same boundaries in related problems

Every cell is read once, so the time is O(R * C). The only extra memory is four integers, so the auxiliary space is O(1). The output list holds R * C values, but it is the answer and does not count as extra memory.

A different method keeps a visited grid and a direction, and turns right when the next cell is outside the matrix or already visited. It produces the same order, but it needs O(R * C) extra memory for visited.

The same four boundaries solve Spiral Matrix II (LC 59), which writes the numbers 1 to n * n into an empty grid in spiral order: replace result.append(...) with an assignment from a counter. Spiral Matrix IV (LC 2326) does the same with the values of a linked list. Rotate Image (LC 48) and Cyclically Rotating a Grid (LC 1914) treat each ring as a closed loop of cells, and move the values along that loop.

60-Second Pitch

"I read the matrix ring by ring with four boundaries: top, bottom, left and right. They enclose exactly the cells that are not in the result yet. Each pass reads the top row, the right column, the bottom row and the left column. After each sweep, I move the boundary on that side inward by 1. The last ring can be one row or one column. So I check top <= bottom before the bottom sweep and left <= right before the left sweep. Without them, a cell would be read twice. Every cell is read once, so the time is O(R * C). I keep four integers, so the extra space is O(1)."

Common Questions

Why are only the bottom sweep and the left sweep guarded?

The while condition guarantees at least one row and one column at the start of the pass. So the top sweep and the right sweep always have cells to read. The top sweep then removes a row and the right sweep removes a column. After that, the rectangle can be empty in one direction, so the bottom sweep and the left sweep each need a check.

What is the time and space complexity of spiral_order?

O(R * C) time, because every cell is added to result once and the boundary updates are constant work. The auxiliary space is O(1): four integers. The output list holds R * C values, and it is the answer.

How does a visited grid compare with the boundary method?

The visited method walks one cell at a time in a direction and turns when the next cell is outside the matrix or already visited. It gives the same order and the same O(R * C) time. It needs an extra grid of R * C flags. The boundary method needs no extra grid.

How do you change the code to fill a grid, as in Spiral Matrix II (LC 59)?

Create an n by n grid of zeros. Use the same four boundaries and the same four sweeps. Replace each result.append(matrix[r][c]) with grid[r][c] = counter, and then add 1 to counter. The guards stay, although for a square grid the only thin ring is the center cell of an odd n.

What changes for Rotate Image (LC 48)?

The task does not read the cells in order. It turns each ring by a quarter. For each i in range(right - left), four cells swap positions through one saved value. A side has right - left + 1 cells, but its last cell starts the next side, so the loop runs right - left times.

SWE Reasoning

Spot it

  • The output order or the update order is a spiral: inward, ring by ring, starting at a corner.
  • The task writes values into a grid in spiral order, as in Spiral Matrix II (LC 59), or reads them in spiral order, as in Spiral Matrix (LC 54).
  • The task moves each ring of a grid as a loop, as in Rotate Image (LC 48) or Cyclically Rotating a Grid (LC 1914).
  • The matrix may have one row, one column or one cell left in the middle, and the code must not read it twice.
  • You are asked for O(1) extra memory, so a visited grid is not allowed.

The invariant, step by step

  1. At the start of a loop pass, top, bottom, left and right enclose exactly the cells not in result. The while condition says this rectangle has at least one row and one column.
  2. The top sweep reads row top from left to right. Then top += 1 removes that row from the rectangle.
  3. The right sweep reads column right from the new top to bottom. Then right -= 1 removes that column.
  4. Before the bottom sweep, if top <= bottom asks whether a row is left. If it is, the sweep reads row bottom from right to left, and bottom -= 1 removes it.
  5. Before the left sweep, if left <= right asks whether a column is left. If it is, the sweep reads column left from bottom to top, and left += 1 removes it.
  6. The four boundaries again enclose exactly the unread cells, so the invariant holds for the next pass. The loop ends when the rectangle has no cell, and result has every cell once.

Traps

  • No top <= bottom guard. On a thin last ring, the bottom sweep reads a row that was already read. For [[1, 2, 3]], the result is [1, 2, 3, 2, 1].
  • No left <= right guard. On a single column, the left sweep reads a column that was already read. For [[1], [2], [3]] without this guard, the result is [1, 2, 3, 2].
  • A boundary is not moved after its sweep. The next sweep then starts on a cell that is already in result, so the corner is read twice.
  • A range that stops one cell early. range(right, left, -1) stops before column left, so the bottom sweep skips the bottom-left corner. Use range(right, left - 1, -1).
  • An empty matrix. matrix[0] raises IndexError for []. The first line, if not matrix or not matrix[0], returns [] instead.

Practice

Canonical LeetCode:Spiral Matrix (LC 54)Spiral Matrix II (LC 59)Rotate Image (LC 48)Spiral Matrix IV (LC 2326)Cyclically Rotating a Grid (LC 1914)
© 2026 Hi👋WebEnterprise. All rights reserved.
Sitemap•llms.txt•