Rotating a Matrix 90°: The Transpose-and-Reverse Trick
Rotating a 2D matrix by 90° is one of those interview problems that looks hard at first and then becomes more obvious once you see the trick. In this article, we’ll explore the underlying logic, derive the index-mapping rule, and then implement it in three languages— Python, JavaScript, and Java. We will also include an in-place version that uses no extra memory. Hopefully, by the end, you’ll have the methods to rotate a matrix clockwise and counter-clockwise in your bag of tricks.
The Problem
Given a square n × n matrix, rotate it 90° clockwise. For example:
Observe what happens to the first column 1, 4, 7. It becomes the first row, but not quite in reversed order. It's the top row read bottom-to-top: 7, 4, 1. That single observation is the whole algorithm.
The dance move: transpose, then reverse
Step 1 — Transpose
Transposing a matrix flips it across its main diagonal (top-left to bottom-right). Every element at position (i, j) swaps with the element at (j, i). Rows become columns.
Step 2— Reverse Each Row
Now reverse the elements in each row.
Now the 90° rotation is complete.
Counter-clockwise is the mirror image
To rotate the matrix 90° counter-clockwise, you just swap the order of the two steps: reverse columns instead of rows:
- Reverse each row first, then transpose, or
- Transpose first, then reverse the row order (reverse the outer list).
The Index-Mapping Rule
We can think about this logic strictly mathematically; there’s a clean formula. For an n × n matrix rotated 90° clockwise, the element at (i, j) moves to:
(i, j) -> (j, n - 1 - i)For counter-clockwise:
(i, j) -> (n - 1 - j, i)This formula is what lets us build a rotated copy in a single pass without the two-step dance. This is useful when the matrix isn’t square (an m × n matrix rotates into an n × m one).
Get Marcus T’s stories in your inbox
Join Medium for free to get updates from this writer.
Python Implementation
Python’s zip(*matrix) transposes a matrix in one expression, and slicing ([::-1]) handles the reversal. That makes the copy-based version delightfully short.
def rotate_matrix(matrix, counter_clockwise=False):
if counter_clockwise:
# Transpose, then reverse the row order.
return [list(row) for row in zip(*matrix)][::-1]
else:
# Reverse the row order, then transpose.
return [list(row) for row in zip(*matrix[::-1])]zip(*matrix) unpacks each row as an argument to zip, which pairs up elements column-wise — that's the transpose. The only quirk is that zip yields tuples, so we wrap each one in list(...).
In-place, O(1) extra space
If the matrix is square and you want to avoid allocating a new one, mutate it directly. Transpose by swapping across the diagonal, then reverse each row.
def rotate_in_place(matrix):
n = len(matrix)
# Transpose: swap across the main diagonal.
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 row in matrix:
row.reverse()Note the inner loop starts at j = i, not j = 0. Swapping only the upper triangle avoids double-swapping (which would put everything back where it started).
JavaScript Implementation
JavaScript has no built-in zip, so we build the rotated copy directly from the index-mapping rule. This version also works for non-square matrices.
function rotateMatrix(matrix, counterClockwise = false) {
const rows = matrix.length;
const cols = matrix[0].length;
const result = [];
if (counterClockwise) {
// Element (i, j) moves to (cols - 1 - j, i)
for (let j = cols - 1; j >= 0; j--) {
const newRow = [];
for (let i = 0; i < rows; i++) {
newRow.push(matrix[i][j]);
}
result.push(newRow);
}
} else {
// Element (i, j) moves to (j, rows - 1 - i)
for (let j = 0; j < cols; j++) {
const newRow = [];
for (let i = rows - 1; i >= 0; i--) {
newRow.push(matrix[i][j]);
}
result.push(newRow);
}
}
return result;
}In-place version
The square in-place version mirrors Python’s — transpose via diagonal swaps, then reverse each row. JavaScript’s array destructuring makes the swap a one-liner.
function rotateInPlace(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 (const row of matrix) {
row.reverse();
}
}Java Implementation
Java makes you work— no zip, no slicing, no destructuring — but the logic is identical. We allocate the result with swapped dimensions (new int[cols][rows]) and apply the mapping rule.
static int[][] rotateMatrix(int[][] matrix, boolean counterClockwise) {
int rows = matrix.length;
int cols = matrix[0].length;
int[][] result = new int[cols][rows];
if (counterClockwise) {
// Element (i, j) moves to (cols - 1 - j, i)
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
result[cols - 1 - j][i] = matrix[i][j];
}
}
} else {
// Element (i, j) moves to (j, rows - 1 - i)
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
result[j][rows - 1 - i] = matrix[i][j];
}
}
}
return result;
}In-place version
static void rotateInPlace(int[][] matrix) {
int n = matrix.length;
// Transpose.
for (int i = 0; i < n; i++) {
for (int j = i; j < n; j++) {
int tmp = matrix[i][j];
matrix[i][j] = matrix[j][i];
matrix[j][i] = tmp;
}
}
// Reverse each row.
for (int[] row : matrix) {
for (int left = 0, right = n - 1; left < right; left++, right--) {
int tmp = row[left];
row[left] = row[right];
row[right] = tmp;
}
}
}Complexity
Every version above touches each element a constant number of times, so:
Approach Time Extra
---------------------------------------
Copy-based O(n²) O(n²)
In-place (square) O(n²) O(1)You can’t beat O(n²) time because you have to move all n² elements. The only real lever is memory: the in-place version rearranges the existing array instead of allocating a new one.
Choosing an approach
- Need a rotated copy or a non-square matrix?
Use the copy-based version with the index-mapping rule. Python’szipmakes it a one-liner; JavaScript and Java build it with a double loop. - Square matrix and memory-constrained?
Use the in-place transpose-and-reverse. It’s the answer interviewers usually want for the classic “rotate image” problem.
Now go work it.









