Image Matrix 90-Degree Twist
You're twenty minutes into a Google interview, and the interviewer pulls up a grid of numbers on the screen. "Imagine this is a pixel grid for an image," they say. "Rotate it 90 degrees clockwise without using extra memory." The room goes quiet for a moment while you sketch out the matrix on your notepad. This problem looks deceptively simple, but the "without extra memory" constraint is what separates a quick brute-force answer from the elegant in-place solution interviewers are looking for. It is a staple at companies like Adobe, Google, Meta, and Amazon, and once you see the underlying trick, you will never forget it.
TL;DR
Rotate an n x n matrix 90 degrees clockwise in-place with two passes: first transpose the matrix by swapping matrix[i][j] with matrix[j][i] along the main diagonal, then reverse each row. This runs in O(n^2) time since every element is visited a constant number of times, and O(1) space because only a single temporary variable is needed for swapping. The key is recognizing that transpose + row reversal composes to a 90-degree clockwise rotation.
Why This Problem Matters
Matrix rotation sits at the intersection of arrays, math, and spatial reasoning. In practice, it shows up in image processing, game development (rotating sprites or tilemaps), and anywhere you need to manipulate 2D grids. In an interview context, it tests whether you can think about coordinate transformations and translate them into clean, bug-free code. It also tests in-place algorithm design, a skill that matters whenever memory is constrained.
Understanding the Problem
You are given an n x n 2D matrix of integers. Your task is to rotate every element 90 degrees clockwise, and you must do it in-place without allocating a second matrix.
Given: An n x n matrix Task: Rotate it 90 degrees clockwise, modifying the original matrix Return: The rotated matrix (for testing verification) Constraint: O(1) extra space
Here is the 3x3 example:
Loading visualization...
After a 90-degree clockwise rotation, the bottom-left corner moves to the top-left, the top-left moves to the top-right, and so on:
Loading visualization...
Notice how each row of the original becomes a column in the result, but in reverse order. The first row [1, 2, 3] becomes the last column, the second row [4, 5, 6] becomes the middle column, and the third row [7, 8, 9] becomes the first column.
Edge Cases to Consider
- 1x1 matrix: Already rotated, return as-is
- 2x2 matrix: Four elements swap in a cycle
- Even vs. odd dimensions: The center element of an odd-sized matrix stays put
- Negative values: The algorithm works on any integer values, so negative numbers require no special handling
Solution Approach
The brute-force solution would allocate a new matrix and copy each element to its rotated position. That works, but uses O(n^2) extra space. The constraint says we need O(1) space.
The observation that unlocks the in-place solution: a 90-degree clockwise rotation is equivalent to two simpler operations applied back-to-back.
- Transpose the matrix (swap elements across the main diagonal)
- Reverse each row
Why does this work? Transposing sends element (i, j) to position (j, i). Reversing row j then sends it to position (j, n-1-i). If you check the math, the mapping (i, j) -> (j, n-1-i) is exactly a 90-degree clockwise rotation.
Loading visualization...
Step 1: Transpose
Transposing means swapping matrix[i][j] with matrix[j][i] for every pair where j > i. You only need to iterate over the upper triangle of the matrix to avoid swapping each pair twice.
For our 3x3 example, the pairs that get swapped are: (0,1) with (1,0), (0,2) with (2,0), and (1,2) with (2,1). Elements on the diagonal stay in place.
Loading visualization...
Green cells sit on the diagonal and did not move. Purple cells were swapped with their mirror across the diagonal. Notice how the rows of the original matrix have become columns.
Step 2: Reverse Each Row
After transposing, reverse each row. For each row, swap the element at index j with the element at index n-1-j, iterating j from 0 to n/2.
Loading visualization...
The highlighted cells at the edges of each row were swapped. The center column stays in place for odd-sized matrices. And there it is, the fully rotated matrix, with no extra memory allocated.
Implementation
Prefer a different language? Jump to solutions in other languages.
Here is the Java solution:
public class Solution {
public int[][] rotate(int[][] matrix) {
int n = matrix.length;
// Step 1: Transpose the matrix
for (int i = 0; i < n; i++) {
for (int j = i; j < n; j++) {
int temp = matrix[i][j];
matrix[i][j] = matrix[j][i];
matrix[j][i] = temp;
}
}
// Step 2: Reverse each row
for (int i = 0; i < n; i++) {
for (int j = 0; j < n / 2; j++) {
int temp = matrix[i][j];
matrix[i][j] = matrix[i][n - 1 - j];
matrix[i][n - 1 - j] = temp;
}
}
return matrix;
}
}
The structure is straightforward: two nested loops for the transpose, two nested loops for the row reversal. Each loop does a simple swap.
Tracing Through the 4x4 Example
For the 4x4 matrix, the same algorithm applies without any changes. Here is the input:
Loading visualization...
And after running the transpose + reverse:
Loading visualization...
The corner elements are highlighted to make it easy to track: 5 moved from (0,0) to (0,3), 11 moved from (0,3) to (3,3), 16 stayed at (3,3) becoming (3,3) only after going through (3,3) -> (3,0) -> ah, let me be precise. The element 15 at (3,0) moved to (0,0), and 5 at (0,0) moved to (0,3). You can verify each corner follows the rotation pattern.
Complexity Analysis
Time Complexity: O(n^2)
The transpose step iterates over roughly n(n-1)/2 pairs in the upper triangle. The reverse step iterates over n * (n/2) elements. Both are proportional to n^2. Since every one of the n^2 elements must move to a new position, O(n^2) is the best we can achieve.
Space Complexity: O(1)
The only extra variable is temp, used during each swap. No auxiliary arrays, hash maps, or recursive call stacks are involved. This constant-space requirement is what makes the problem interesting.
Alternative Approaches
- Allocate a new matrix: Copy element (i, j) to position (j, n-1-i) in a new matrix. O(n^2) time but O(n^2) space. Does not satisfy the in-place constraint.
- Four-way cyclic swap: For each cell in the top-left quadrant, rotate four corresponding cells simultaneously. Same O(n^2) time and O(1) space, but the index arithmetic is trickier and more prone to off-by-one errors.
- Reverse rows then transpose: This produces a 90-degree counterclockwise rotation instead. The order of operations matters.
Common Pitfalls
-
Transposing the full matrix instead of the upper triangle: If you loop both i and j from 0 to n-1, you swap every pair twice, leaving the matrix unchanged. The inner loop must start at j = i (or j = i+1 in some implementations).
// Wrong: swaps every element twice, matrix ends up unchanged for (int i = 0; i < n; i++) { for (int j = 0; j < n; j++) { int temp = matrix[i][j]; matrix[i][j] = matrix[j][i]; matrix[j][i] = temp; } } -
Reversing the wrong axis: Reversing columns instead of rows produces a counterclockwise rotation. Double-check by tracing a corner element.
-
Off-by-one in the reverse loop: The inner loop should run from j = 0 to j < n/2. Using
j <= n/2for odd-sized matrices swaps the middle element with itself, which is harmless, but for even-sized matrices it could cause a double-swap. -
Confusing clockwise with counterclockwise: Transpose + reverse rows = clockwise. Reverse rows + transpose = counterclockwise. A quick test with a 2x2 matrix confirms which is which.
Interview Tips
When this problem comes up in an interview:
-
Start with the brute-force: Mention that you could allocate a new matrix and copy elements to their rotated positions. This shows you understand the problem before optimizing.
-
Explain the two-step insight: "A 90-degree clockwise rotation is the same as transposing and then reversing each row." Deriving this on the whiteboard earns strong marks.
-
Trace a small example: Walk through a 3x3 matrix step by step. Show the state after transpose and again after row reversal. This catches bugs before they make it into code.
-
Know the variants: If the interviewer asks about counterclockwise rotation, you can swap the operation order (reverse first, then transpose) or transpose then reverse columns. For 180-degree rotation, apply the 90-degree rotation twice or reverse the matrix vertically then horizontally.
-
Mention the four-way swap alternative: Even if you go with transpose + reverse, showing awareness of the cyclic approach demonstrates depth.
Key Takeaways
- A 90-degree clockwise rotation equals transpose + reverse each row. This two-step decomposition avoids allocating extra memory and keeps space at O(1).
- The transpose step must only iterate the upper triangle (j starts at i, not 0). Iterating the full matrix swaps each pair twice and leaves it unchanged.
- Time complexity is O(n^2) and cannot be improved, because every element in the n x n matrix must be relocated.
- For counterclockwise rotation, swap the order: reverse each row first, then transpose. Knowing both variants shows interviewers you understand the underlying linear algebra.
- Always trace through a small example (3x3 is ideal) before coding. The index math in matrix problems is where most bugs hide.
Solutions in Other Languages
Python
class Solution:
def rotate(self, matrix: list[list[int]]) -> list[list[int]]:
n = len(matrix)
# Transpose
for i in range(n):
for j in range(i, n):
matrix[i][j], matrix[j][i] = matrix[j][i], matrix[i][j]
# Reverse each row
for i in range(n):
matrix[i].reverse()
return matrix
Python's tuple swap makes the transpose step particularly concise. The built-in reverse() method handles row reversal in-place.
JavaScript
class Solution {
rotate(matrix) {
const n = matrix.length;
// Transpose
for (let i = 0; i < n; i++) {
for (let j = i; j < n; j++) {
[matrix[i][j], matrix[j][i]] = [matrix[j][i], matrix[i][j]];
}
}
// Reverse each row
for (let i = 0; i < n; i++) {
matrix[i].reverse();
}
return matrix;
}
}
Destructuring assignment handles the swap cleanly, and Array.prototype.reverse() reverses each row in-place.
TypeScript
class Solution {
rotate(matrix: number[][]): number[][] {
const n = matrix.length;
for (let i = 0; i < n; i++) {
for (let j = i; j < n; j++) {
[matrix[i][j], matrix[j][i]] = [matrix[j][i], matrix[i][j]];
}
}
for (let i = 0; i < n; i++) {
matrix[i].reverse();
}
return matrix;
}
}
Identical logic to the JavaScript solution with type annotations added.
C++
#include <algorithm>
#include <vector>
class Solution {
public:
std::vector<std::vector<int>> rotate(std::vector<std::vector<int>> &matrix) {
int n = matrix.size();
// Transpose
for (int i = 0; i < n; ++i) {
for (int j = i + 1; j < n; ++j) {
std::swap(matrix[i][j], matrix[j][i]);
}
}
// Reverse each row
for (int i = 0; i < n; ++i) {
std::reverse(matrix[i].begin(), matrix[i].end());
}
return matrix;
}
};
The C++ solution uses std::swap and std::reverse from the standard library. Note that the inner loop starts at j = i + 1 rather than j = i to skip the self-swap on the diagonal.
Go
func (s *Solution) Rotate(matrix [][]int) [][]int {
n := len(matrix)
// Transpose
for i := 0; i < n; i++ {
for j := i; j < n; j++ {
matrix[i][j], matrix[j][i] = matrix[j][i], matrix[i][j]
}
}
// Reverse each row
for i := 0; i < n; i++ {
for j := 0; j < n/2; j++ {
matrix[i][j], matrix[i][n-j-1] = matrix[i][n-j-1], matrix[i][j]
}
}
return matrix
}
Go does not have a built-in slice reverse, so the row reversal uses a manual two-pointer swap.
Scala
class Solution {
def rotate(matrix: Array[Array[Int]]): Array[Array[Int]] = {
val n = matrix.length
for (i <- 0 until n) {
for (j <- i until n) {
val temp = matrix(i)(j)
matrix(i)(j) = matrix(j)(i)
matrix(j)(i) = temp
}
}
for (i <- 0 until n) {
for (j <- 0 until n / 2) {
val temp = matrix(i)(j)
matrix(i)(j) = matrix(i)(n - 1 - j)
matrix(i)(n - 1 - j) = temp
}
}
matrix
}
}
Uses mutable Array[Array[Int]] for in-place modification. The range 0 until n is exclusive on the upper bound, matching the required loop boundaries.
Kotlin
class Solution {
fun rotate(matrix: Array<IntArray>): Array<IntArray> {
val n = matrix.size
for (i in 0 until n) {
for (j in i until n) {
val temp = matrix[i][j]
matrix[i][j] = matrix[j][i]
matrix[j][i] = temp
}
}
for (i in 0 until n) {
for (j in 0 until n / 2) {
val temp = matrix[i][j]
matrix[i][j] = matrix[i][n - 1 - j]
matrix[i][n - 1 - j] = temp
}
}
return matrix
}
}
Ruby
class Solution
def rotate(matrix)
n = matrix.length
(0...n).each do |i|
(i...n).each do |j|
matrix[i][j], matrix[j][i] = matrix[j][i], matrix[i][j]
end
end
(0...n).each do |i|
(0...(n / 2)).each do |j|
matrix[i][j], matrix[i][n - 1 - j] = matrix[i][n - 1 - j], matrix[i][j]
end
end
matrix
end
end
Ruby's parallel assignment handles swaps neatly. The triple-dot range (0...n) is exclusive, matching the loop boundaries.
Rust
pub fn rotate(mut matrix: Vec<Vec<i32>>) -> Vec<Vec<i32>> {
let n = matrix.len();
for i in 0..n {
for j in i..n {
let temp = matrix[i][j];
matrix[i][j] = matrix[j][i];
matrix[j][i] = temp;
}
}
for i in 0..n {
for j in 0..n / 2 {
let temp = matrix[i][j];
matrix[i][j] = matrix[i][n - 1 - j];
matrix[i][n - 1 - j] = temp;
}
}
matrix
}
Rust requires the mut keyword since we modify the matrix in-place. The borrow checker is satisfied because each swap accesses distinct indices.
Swift
class Solution {
func rotate(_ matrix: [[Int]]) -> [[Int]] {
var matrix = matrix
let n = matrix.count
for i in 0..<n {
for j in i..<n {
let temp = matrix[i][j]
matrix[i][j] = matrix[j][i]
matrix[j][i] = temp
}
}
for i in 0..<n {
for j in 0..<n / 2 {
let temp = matrix[i][j]
matrix[i][j] = matrix[i][n - 1 - j]
matrix[i][n - 1 - j] = temp
}
}
return matrix
}
}
Swift arrays are value types, so the var matrix = matrix line creates a mutable local copy. The half-open range 0..<n matches the required loop bounds.
C#
public class Solution {
public int[][] rotate(int[][] matrix) {
var n = matrix.Length;
for (var i = 0; i < n; i++) {
for (var j = i; j < n; j++) {
var temp = matrix[i][j];
matrix[i][j] = matrix[j][i];
matrix[j][i] = temp;
}
}
for (var i = 0; i < n; i++) {
for (var j = 0; j < n / 2; j++) {
var temp = matrix[i][j];
matrix[i][j] = matrix[i][n - 1 - j];
matrix[i][n - 1 - j] = temp;
}
}
return matrix;
}
}
Dart
class Solution {
List<List<int>> rotate(List<List<int>> matrix) {
int n = matrix.length;
for (int i = 0; i < n; i++) {
for (int j = i; j < n; j++) {
int temp = matrix[i][j];
matrix[i][j] = matrix[j][i];
matrix[j][i] = temp;
}
}
for (int i = 0; i < n; i++) {
for (int j = 0; j < n ~/ 2; j++) {
int temp = matrix[i][j];
matrix[i][j] = matrix[i][n - 1 - j];
matrix[i][n - 1 - j] = temp;
}
}
return matrix;
}
}
Dart uses ~/ for integer division, ensuring the loop bound is computed correctly for both even and odd matrix sizes.
PHP
class Solution {
public function rotate(array $matrix): array {
$n = count($matrix);
for ($i = 0; $i < $n; $i++) {
for ($j = $i; $j < $n; $j++) {
$temp = $matrix[$i][$j];
$matrix[$i][$j] = $matrix[$j][$i];
$matrix[$j][$i] = $temp;
}
}
for ($i = 0; $i < $n; $i++) {
$matrix[$i] = array_reverse($matrix[$i]);
}
return $matrix;
}
}
PHP's array_reverse creates a new array, so this is not strictly in-place at the row level. For a fully in-place version, replace it with a manual swap loop.
Practice Makes Perfect
Once you are comfortable with matrix rotation, these related problems are natural next steps:
- Spiral Matrix - Traverse a matrix in spiral order, which uses similar boundary-tracking logic
- Set Matrix Zeroes - Another in-place matrix transformation with constrained space
- Transpose Matrix - Practice just the transpose step on non-square matrices
- Rotate Array - Apply the reversal trick to a 1D array
Remember, consistent practice is the key to succeeding in coding interviews. This problem and thousands of others are available on Firecode, where over 50,000 users have successfully prepared for technical interviews and landed six and seven-figure jobs at top tech companies. Whether you are just starting or preparing for your next big interview, building fluency with matrix manipulation will pay dividends.
Happy coding, and may all your rotations land right-side up!