Rotate Image
Master in-place matrix rotation—the same 2D transformation pattern that powers image and spectrogram augmentations in modern ML systems.
Problem Statement
You are given an n x n 2D matrix representing an image. Rotate the image by 90 degrees clockwise, in-place.
In other words:
- Input:
matrix[i][j]is the pixel at rowi, columnj. - Output: after rotation,
matrix[i][j]should hold the pixel that was at its new corresponding position. - You must not allocate another
n x nmatrix; modifymatrixitself.
Examples
Example 1
matrix = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
]
After rotation:
[
[7, 4, 1],
[8, 5, 2],
[9, 6, 3]
]
Example 2
matrix = [
[ 5, 1, 9, 11],
[ 2, 4, 8, 10],
[13, 3, 6, 7],
[15, 14, 12, 16]
]
After rotation:
[
[15, 13, 2, 5],
[14, 3, 4, 1],
[12, 6, 8, 9],
[16, 7, 10, 11]
]
Constraints
n == len(matrix) == len(matrix[0])(the matrix is square)1 <= n <= 1000(online judges often use smaller limits, but your algorithm should scale)-10⁴ <= matrix[i][j] <= 10⁴
Understanding the Problem
Imagine the matrix as a grid of coordinates ((r, c)) where:
ris the row index (0at the top),cis the column index (0at the left).
For a clockwise 90° rotation:
[ (r, c) \longrightarrow (c,\ n - 1 - r) ]
Visually for a 3×3 matrix:
Original indices: After 90° CW:
(0,0) (0,1) (0,2) (2,0) (1,0) (0,0)
(1,0) (1,1) (1,2) → (2,1) (1,1) (0,1)
(2,0) (2,1) (2,2) (2,2) (1,2) (0,2)
Rotating is just moving values to new coordinates. The difficulty comes from:
- Doing it in-place (no extra
n x nbuffer), - Avoiding accidental overwrites,
- Handling all elements exactly once.
Why This Problem Matters
Beyond the coding challenge, this pattern appears all over:
- Vision data augmentation: rotating input images by multiples of 90°.
- Tensor layout transforms: e.g., changing from
[H, W, C]to[W, H, C]or performing block rotations. - Spectrogram manipulations: operating on time–frequency matrices in speech pipelines.
- GPU kernels: where you must transform indices without extra memory.
Understanding how to rotate in-place is basically learning to think in 2D index space and reason about what “in-place” means for a structured object.
Approach 1: Extra Matrix (Not In-Place, For Intuition)
Intuition
The simplest way to rotate is to allocate a new matrix and write:
rotated[c][n - 1 - r] = matrix[r][c]
Then copy rotated back into matrix. This is not allowed by the problem’s in-place constraint but is useful to:
- Verify your understanding of the index mapping,
- Generate expected outputs for testing your in-place implementation.
Implementation
from typing import List
def rotate_with_copy(matrix: List[List[int]]) -> None:
\"\"\"Rotate 90° clockwise using an extra matrix (NOT in-place).
Time: O(n^2)
Space: O(n^2) extra
\"\"\"
n = len(matrix)
rotated = [[0] * n for _ in range(n)]
for r in range(n):
for c in range(n):
rotated[c][n - 1 - r] = matrix[r][c]
# Copy back into original matrix
for r in range(n):
for c in range(n):
matrix[r][c] = rotated[r][c]
Why We Need More
This violates the “no extra matrix” requirement:
- Extra space is
O(n²)instead ofO(1). - In a real system working with huge images or feature maps, duplicating entire tensors can be too expensive.
We want an algorithm that:
- Has the same time complexity,
- But uses only constant extra space (a few scalars/temporaries).
Approach 2: Transpose + Reverse (Elegant In-Place)
Key Idea
A 90° clockwise rotation can be decomposed into two simpler operations:
- Transpose the matrix (swap
matrix[r][c]withmatrix[c][r]). - Reverse each row.
For example, starting from:
1 2 3
4 5 6
7 8 9
Transpose across the main diagonal:
1 4 7
2 5 8
3 6 9
Then reverse each row:
7 4 1
8 5 2
9 6 3
Which is the desired rotated result.
Why This Works (Coordinate Proof)
Transpose does:
[ (r, c) \rightarrow (c, r) ]
Reversing each row (index c along the row) does:
[ (c, r) \rightarrow (c,\ n - 1 - r) ]
Composing them:
[ (r, c) \xrightarrow{\text{transpose}} (c, r) \xrightarrow{\text{reverse rows}} (c, n-1-r) ]
Which matches the rotation formula.
Implementation
from typing import List
def rotate(matrix: List[List[int]]) -> None:
\"\"\"Rotate the matrix 90 degrees clockwise in-place.
Uses transpose + row reversal.
Time: O(n^2)
Space: O(1) extra
\"\"\"
n = len(matrix)
# 1. Transpose in-place
for r in range(n):
# only swap above the diagonal to avoid double-swapping
for c in range(r + 1, n):
matrix[r][c], matrix[c][r] = matrix[c][r], matrix[r][c]
# 2. Reverse each row
for r in range(n):
matrix[r].reverse()
Step-by-Step Example
For matrix = [[1,2,3],[4,5,6],[7,8,9]]:
- Transpose
- swap
(0,1)with(1,0)→[[1,4,3],[2,5,6],[7,8,9]] - swap
(0,2)with(2,0)→[[1,4,7],[2,5,6],[3,8,9]] - swap
(1,2)with(2,1)→[[1,4,7],[2,5,8],[3,6,9]]
- swap
- Reverse each row
[1,4,7]→[7,4,1][2,5,8]→[8,5,2][3,6,9]→[9,6,3]
Result:
[
[7,4,1],
[8,5,2],
[9,6,3]
]
Edge Cases
- n = 1
- Matrix is
[[x]]; rotation does nothing. Our code handles this naturally.
- Matrix is
- n = 2
- Example:
[[1,2], [3,4]]After transpose:
[[1,3],[2,4]]; after row reverse:[[3,1],[4,2]].
- Example:
- Non-square matrix
- The problem definition assumes square; if you needed to support
m x nmatrices, transpose+reverse alone wouldn’t be enough—this is a different problem (rotate image is defined for square).
- The problem definition assumes square; if you needed to support
Approach 3: Layer-by-Layer 4-Way Swaps
Intuition
Instead of doing global operations (transpose & reverse), we can rotate the matrix in-place by processing one “ring” (layer) at a time.
For a 4×4 matrix:
Layer 0 (outer ring): indices where min(r, c) = 0
Layer 1 (inner ring): indices where min(r, c) = 1
Within each layer, we perform 4-way swaps:
top → right
left → top
bottom → left
right → bottom
More concretely, for a cell at (row, col):
- Its right partner is
(col, n-1-row), - Its bottom partner is
(n-1-row, n-1-col), - Its left partner is
(n-1-col, row).
We can pick the “top” of each quadruple, save it, then rotate the other 3 values, and finally put the saved top into the right position.
Implementation
from typing import List
def rotate_layers(matrix: List[List[int]]) -> None:
\"\"\"Rotate matrix 90° clockwise in-place using layer-by-layer 4-way swaps.
Time: O(n^2)
Space: O(1)
\"\"\"\n n = len(matrix)
# Process layers from outermost to innermost
for layer in range(n // 2):
first = layer
last = n - 1 - layer
for i in range(first, last):
offset = i - first
# Save top
top = matrix[first][i]
# left -> top
matrix[first][i] = matrix[last - offset][first]
# bottom -> left
matrix[last - offset][first] = matrix[last][last - offset]
# right -> bottom
matrix[last][last - offset] = matrix[i][last]
# top -> right
matrix[i][last] = top
This approach is closer to how you might implement a low-level kernel or a special-case transform on a hardware accelerator.
Comparing Approaches
| Approach | Time | Space | Notes |
|---|---|---|---|
| Extra matrix | O(n²) | O(n²) | Easiest to understand, not in-place |
| Transpose + reverse | O(n²) | O(1) | Clean, idiomatic, recommended in interviews |
| Layer-by-layer 4-way | O(n²) | O(1) | More index-heavy but very instructive |
Implementation: Utilities and Tests
from typing import List
def rotate_in_place(matrix: List[List[int]]) -> None:
\"\"\"Wrapper for the chosen production implementation."""
# Choose one: rotate (transpose+reverse) or rotate_layers
rotate(matrix)
def test_rotate():
tests = [
(
[[1, 2, 3],
[4, 5, 6],
[7, 8, 9]],
[[7, 4, 1],
[8, 5, 2],
[9, 6, 3]],
),
(
[[5, 1, 9, 11],
[2, 4, 8, 10],
[13, 3, 6, 7],
[15, 14, 12, 16]],
[[15, 13, 2, 5],
[14, 3, 4, 1],
[12, 6, 8, 9],
[16, 7, 10, 11]],
),
(
[[1]],
[[1]],
),
(
[[1, 2],
[3, 4]],
[[3, 1],
[4, 2]],
),
]
for i, (matrix, expected) in enumerate(tests, 1):
rotate_in_place(matrix)
assert matrix == expected, f\"Test {i} failed: {matrix} != {expected}\"
print(\"All rotate tests passed.\")
if __name__ == \"__main__\":
test_rotate()
You can strengthen the tests by adding:
- Random matrices and comparing against the
rotate_with_copyimplementation. - Larger sizes (e.g.,
10x10,50x50) to catch off-by-one errors.
Complexity Analysis
Let (n) be the dimension of the square matrix.
Time Complexity
- Every approach touches each element a constant number of times.
- Transpose: about (n(n-1)/2) swaps.
- Reverse rows: (n \times (n/2)) swaps.
- Layer-based: each element is moved exactly once within a 4-cycle.
- So total time is:
[ T(n) = \Theta(n^2) ]
Space Complexity
- We are only using a constant number of scalar temporaries.
- No extra
n x nmatrix is allocated in the in-place solutions. - Therefore:
[ S(n) = O(1)\ \text{(extra space)} ]
Production Considerations
Even if you never write your own rotation kernel in a production codebase, the ideas here show up in many ML systems:
1. Image Data Augmentation
- Many training pipelines (e.g., for vision models) apply random rotations:
- 90°, 180°, 270° (cheap, can be implemented as index remaps),
- Arbitrary-angle rotations (involving interpolation).
- Libraries like torchvision, PIL, OpenCV implement these transforms, but inside they rely on exactly this kind of index mapping or on more advanced geometric transforms.
- In performance-critical settings (e.g., training on millions of high-res images), understanding how to do these operations with minimal copies and cache-friendly memory access matters.
2. Tensor Layout Transforms
Frameworks and kernels often need to transform tensor layouts, for example:
[batch, height, width, channels](NHWC) ↔[batch, channels, height, width](NCHW).- Blocking and tiling for better cache and vectorization.
These are not literal rotations, but they are permutation of axes and indices:
- You still have to compute a mapping from source
(i, j, k, ...)to destination(i', j', k', ...). - Thinking clearly about index math (like we did for rotation) is key to avoiding subtle off-by-one and alignment bugs.
3. Spectrogram Manipulation in Speech
ASR and other speech models often operate on time–frequency matrices:
- Time masking and frequency masking (SpecAugment),
- Time warping,
- Per-frequency scaling or normalization.
These are 2D operations on matrices with the same flavor as rotation:
- Masking a frequency band is just zeroing out rows in a given index range.
- Time warping is a more complex mapping from input time indices to output time indices.
If you are comfortable with simple 2D transforms like rotation, it’s much easier to read and design these spectrogram-level augmentations.
Interview Strategy
How to Present Your Solution
When walking through this problem in an interview:
- Clarify constraints
- Confirm the matrix is square.
- Confirm that the rotation is exactly 90° clockwise (not arbitrary angle).
- Confirm that in-place is required (no extra
n x nmatrix).
- Start with the naive copy-based approach
- Show you understand the mapping
(r, c) → (c, n-1-r). - Explicitly state the extra-space cost and why it violates the requirement.
- Show you understand the mapping
- Propose the in-place strategy
- Option 1: transpose + reverse rows.
- Option 2: layer-by-layer 4-way swap.
- Explain why you’re choosing one (simplicity vs demonstration of pointer/index mastery).
- Code with care
- Emphasize avoiding double-swaps in transpose (
cstarts atr+1). - Use descriptive variable names (
first,last,offset) in layer-based solution.
- Emphasize avoiding double-swaps in transpose (
- Discuss complexity & edge cases
- Time
O(n²), spaceO(1). - Mention
n=1,n=2, and why they work without special handling.
- Time
- Connect to real systems
- Briefly mention that the same patterns are used in image augmentation and tensor layout transforms.
Common Pitfalls to Avoid
- Wrong index math in layer-based approach—especially mixing up
rowvscol. - Double-swapping during transpose (if you loop over all
(r, c)instead of half the matrix). - Allocating extra matrices and ignoring the in-place requirement.
Additional Practice & Variants
To really lock in this pattern, try the following related problems:
- Rotate 90° counter-clockwise
- Derive the mapping: [ (r, c) \rightarrow (n - 1 - c,\ r) ]
- Implement in-place using transpose + column reversal, or adjust the layer-based swaps.
- Rotate 180° in-place
- Two 90° rotations, or directly: [ (r, c) \rightarrow (n - 1 - r,\ n - 1 - c) ]
- Implement a single pass that swaps
(r, c)with(n-1-r, n-1-c)for half the matrix.
- Rotate arbitrary k times
- For k (possibly negative), compute
k_mod = ((k % 4) + 4) % 4. - Apply
rotatek_modtimes (0 = no-op, 1 = 90°, 2 = 180°, 3 = 270°).
- For k (possibly negative), compute
- Rotate sub-matrices
- Given a large matrix and many queries
(r1, c1, r2, c2), rotate only the sub-square. - Apply the same logic but offset indices by
(r1, c1).
- Given a large matrix and many queries
- In-place transpose without using a temporary
- Practice writing a standalone in-place transpose for a square matrix.
- This appears directly in many ML kernels and is a good building block for more complex transforms.
These variants help you move from “I solved one LeetCode problem” to “I can systematically reason about in-place 2D array transforms,” which is exactly the kind of skill that shows up in senior interviews and real-world ML engineering.
Key Takeaways
✅ A 90° rotation is just a deterministic mapping of coordinates—getting the index math right is the core.
✅ You can implement rotation in-place using transpose + row reversal or more manual layer-based 4-way swaps.
✅ The runtime is (\Theta(n²)) and extra space is (O(1)), which scales well even for large matrices.
✅ Many ML and systems tasks boil down to 2D/ND tensor transformations that use the same reasoning.
✅ Practicing these transforms pays off directly in writing and debugging performance-critical data and model pipelines.
Originally published at: arunbaby.com/dsa/0018-rotate-image
If you found this helpful, consider sharing it with others who might benefit.