Rotate a square image clockwise by 90 degrees
You're sitting in a Microsoft interview and the whiteboard shows a grid of numbers. "Think of this as a photo," the interviewer says. "How would you rotate it 90 degrees clockwise?" This is the Rotate Image problem, also known as "Rotate Image" on other platforms, and it's a favorite at companies like Amazon, Microsoft, and Cisco. It tests your ability to reason about 2D indices and, more importantly, your skill at decomposing a complex transformation into smaller, manageable pieces.
TL;DR
Rotating a square matrix clockwise by 90 degrees is equivalent to first transposing the matrix (swapping rows and columns) and then flipping the result along its vertical axis (reversing each row). Both operations are straightforward nested loops. The combined approach runs in O(m * n) time and O(m * n) space when creating a new output matrix. The key insight is that this decomposition turns one confusing index mapping into two simple ones.
Why This Problem Matters
Matrix rotation sits at the intersection of array manipulation and spatial reasoning. Once you understand how to decompose rotation into transpose-plus-flip, you gain a reusable mental model for other matrix transformations: counterclockwise rotation, 180-degree rotation, mirroring, and more. Interviewers love this problem because candidates who see the decomposition immediately demonstrate strong problem-solving instincts.
Understanding the Problem
Given a square image represented as a 2D array of integers (pixel values between 0 and 255), return a new matrix that is the original image rotated clockwise by 90 degrees.
Here is the example from the problem statement. The 1 in the top-left corner moves to the top-right after rotation:
Original:
Loading visualization...
After 90-degree clockwise rotation:
Loading visualization...
Constraints
- The image will have at least 1 pixel.
- The image is always square (same width and height).
Edge Cases
- 1x1 matrix: Already rotated. Return it unchanged.
- 2x2 matrix: The simplest non-trivial case, good for tracing your algorithm.
- Odd dimensions: The center element stays in place.
The Decomposition Insight
If you try to derive the direct index mapping for a 90-degree clockwise rotation, you get rotated[col][n - 1 - row] = original[row][col]. That formula is correct but easy to get wrong under interview pressure.
A better approach: break rotation into two well-understood operations.
Step 1: Transpose - swap rows and columns, so transposed[row][col] = original[col][row].
Loading visualization...
Step 2: Flip on vertical axis - reverse each row, so flipped[row][col] = transposed[row][n - 1 - col].
Loading visualization...
Chaining these two operations produces a clockwise 90-degree rotation. Let's verify this with a 3x3 matrix.
Walking Through a 3x3 Example
Original matrix:
Loading visualization...
After transpose (rows become columns, columns become rows). Notice how 2 and 4 swapped positions, 3 and 7 swapped, and 6 and 8 swapped. The diagonal elements (1, 5, 9) stay put:
Loading visualization...
After flipping on the vertical axis (each row reversed). This is the final rotated result:
Loading visualization...
Reading the result column by column from left to right gives us 7-8-9, 4-5-6, 1-2-3, which is exactly the original matrix read bottom-to-top. That confirms our rotation is correct.
Implementation
Prefer a different language? Jump to solutions in other languages.
The approach uses two helper methods. transpose creates a new matrix where rows and columns are swapped. flipOnVerticalAxis creates a new matrix where each row is reversed. The main method simply chains them together.
public class Solution {
public int[][] rotateClockwise(int[][] image) {
return flipOnVerticalAxis(transpose(image));
}
private int[][] transpose(int[][] image) {
int size = image.length;
int[][] transposed = new int[size][size];
for (int row = 0; row < size; row++) {
for (int col = 0; col < size; col++) {
transposed[row][col] = image[col][row];
}
}
return transposed;
}
private int[][] flipOnVerticalAxis(int[][] image) {
int numRows = image.length, numCols = image[0].length;
int[][] flipped = new int[numRows][numCols];
for (int row = 0; row < numRows; row++) {
for (int col = 0; col < numCols; col++) {
flipped[row][col] = image[row][numCols - 1 - col];
}
}
return flipped;
}
}
Let's trace through the 2x2 example [[1, 0], [0, 0]]:
Transpose step:
transposed[0][0] = image[0][0] = 1transposed[0][1] = image[1][0] = 0transposed[1][0] = image[0][1] = 0transposed[1][1] = image[1][1] = 0- Result:
[[1, 0], [0, 0]](this particular matrix happens to be symmetric)
Flip step:
flipped[0][0] = transposed[0][1] = 0flipped[0][1] = transposed[0][0] = 1flipped[1][0] = transposed[1][1] = 0flipped[1][1] = transposed[1][0] = 0- Result:
[[0, 1], [0, 0]]
The 1 moved from top-left to top-right, exactly a 90-degree clockwise rotation.
Complexity Analysis
Time Complexity: O(m * n)
Both transpose and flipOnVerticalAxis iterate over every cell in the matrix once, performing O(1) work per cell. Two passes through m * n cells gives O(2 * m * n), which simplifies to O(m * n). For a square matrix of size n, this is O(n^2).
Space Complexity: O(m * n)
We allocate two new matrices (one for the transpose, one for the flip), each of size m * n. The transpose matrix becomes eligible for garbage collection after flipOnVerticalAxis returns, but peak memory usage is O(m * n) for the output. You could reduce this to a single allocation by combining both operations into one pass, mapping directly from image[col][row] reversed.
In-Place Alternative
An in-place rotation is possible by transposing the matrix in-place (only swapping the upper triangle with the lower triangle) and then reversing each row in-place. This brings space down to O(1) but modifies the original input, which may not be acceptable depending on requirements.
Common Pitfalls
-
Getting the index mapping backwards:
transposed[row][col] = image[col][row], notimage[row][col]. Double-check by tracing a single element. -
Off-by-one in the flip: The reversed column index is
numCols - 1 - col, notnumCols - col. Missing the-1will cause anArrayIndexOutOfBoundsException. -
Confusing clockwise with counterclockwise: Clockwise is transpose then flip vertically. Counterclockwise is transpose then flip horizontally. Mixing these up produces the wrong rotation direction.
-
Assuming square in flip: Although the problem guarantees a square image, writing
flipOnVerticalAxisto handle rectangular matrices (using separatenumRowsandnumCols) makes the helper more general and avoids bugs if requirements change.
Interview Tips
When solving this in an interview:
-
Start by drawing a small example. A 3x3 grid with values 1-9 makes the rotation visually obvious and helps you verify your index mapping.
-
Mention the decomposition early. Saying "rotation equals transpose plus flip" shows the interviewer you see the elegant approach rather than brute-forcing index arithmetic.
-
Discuss the in-place variant. Even if you implement the O(n) space version, mentioning that transpose can be done in-place by swapping only the upper triangle demonstrates deeper understanding.
-
Handle follow-ups confidently: "What about counterclockwise?" (transpose then flip horizontally). "What about 180 degrees?" (two 90-degree rotations, or reverse all rows then reverse each row).
Solutions in Other Languages
Python
from typing import List
class Solution:
def rotate_clockwise(self, image: List[List[int]]) -> List[List[int]]:
return self.flip_on_vertical_axis(self.transpose(image))
def transpose(self, image: List[List[int]]) -> List[List[int]]:
size = len(image)
transposed = [[0 for _ in range(size)] for _ in range(size)]
for row in range(size):
for col in range(size):
transposed[row][col] = image[col][row]
return transposed
def flip_on_vertical_axis(self, image: List[List[int]]) -> List[List[int]]:
num_rows = len(image)
num_cols = len(image[0])
flipped = [[0 for col in range(num_cols)] for row in range(num_rows)]
for row in range(num_rows):
for col in range(num_cols):
flipped[row][col] = image[row][num_cols - 1 - col]
return flipped
JavaScript
class Solution {
rotateClockwise(image) {
return this.flipOnVerticalAxis(this.transpose(image));
}
transpose(image) {
const size = image.length;
const transposed = [...Array(size)].map(_ => Array(size));
for (let row = 0; row < size; row++) {
for (let col = 0; col < size; col++) {
transposed[row][col] = image[col][row];
}
}
return transposed;
}
flipOnVerticalAxis(image) {
const numRows = image.length, numCols = image[0].length;
const flipped = [...Array(numRows)].map(_ => Array(numCols));
for (let row = 0; row < numRows; row++) {
for (let col = 0; col < numCols; col++) {
flipped[row][col] = image[row][numCols - 1 - col];
}
}
return flipped;
}
}
TypeScript
class Solution {
rotateClockwise(image: number[][]): number[][] {
return this.flipOnVerticalAxis(this.transpose(image));
}
transpose(image: number[][]): number[][] {
const size = image.length;
const transposed: number[][] = [...Array(size)].map(_ => Array(size));
for (let row = 0; row < size; row++) {
for (let col = 0; col < size; col++) {
transposed[row][col] = image[col][row];
}
}
return transposed;
}
flipOnVerticalAxis(image: number[][]): number[][] {
const numRows = image.length, numCols = image[0].length;
const flipped: number[][] = [...Array(numRows)].map(_ => Array(numCols));
for (let row = 0; row < numRows; row++) {
for (let col = 0; col < numCols; col++) {
flipped[row][col] = image[row][numCols - 1 - col];
}
}
return flipped;
}
}
C++
#include <vector>
class Solution {
public:
std::vector<std::vector<int>> rotateClockwise(const std::vector<std::vector<int>> &image) {
return flipOnVerticalAxis(transpose(image));
}
private:
std::vector<std::vector<int>> transpose(const std::vector<std::vector<int>> &image) {
int size = image.size();
std::vector<std::vector<int>> transposed(size, std::vector<int>(size));
for (int row = 0; row < size; row++) {
for (int col = 0; col < size; col++) {
transposed[row][col] = image[col][row];
}
}
return transposed;
}
std::vector<std::vector<int>> flipOnVerticalAxis(const std::vector<std::vector<int>> &image) {
int numRows = image.size(), numCols = image[0].size();
std::vector<std::vector<int>> flipped(numRows, std::vector<int>(numCols));
for (int row = 0; row < numRows; row++) {
for (int col = 0; col < numCols; col++) {
flipped[row][col] = image[row][numCols - 1 - col];
}
}
return flipped;
}
};
Go
func (s *Solution) RotateClockwise(image [][]int) [][]int {
return s.flipOnVerticalAxis(transpose(image))
}
func transpose(image [][]int) [][]int {
size := len(image)
transposed := make([][]int, size)
for i := range transposed {
transposed[i] = make([]int, size)
}
for row := 0; row < size; row++ {
for col := 0; col < size; col++ {
transposed[row][col] = image[col][row]
}
}
return transposed
}
func (s *Solution) flipOnVerticalAxis(image [][]int) [][]int {
numRows, numCols := len(image), len(image[0])
flipped := make([][]int, numRows)
for i := range flipped {
flipped[i] = make([]int, numCols)
}
for row := 0; row < numRows; row++ {
for col := 0; col < numCols; col++ {
flipped[row][col] = image[row][numCols-1-col]
}
}
return flipped
}
Scala
class Solution {
def rotateClockwise(image: Array[Array[Int]]): Array[Array[Int]] = {
flipOnVerticalAxis(transpose(image))
}
private def transpose(image: Array[Array[Int]]): Array[Array[Int]] = {
val size = image.length
val transposed = Array.ofDim[Int](size, size)
for (row <- 0 until size) {
for (col <- 0 until size) {
transposed(row)(col) = image(col)(row)
}
}
transposed
}
private def flipOnVerticalAxis(image: Array[Array[Int]]): Array[Array[Int]] = {
val numRows = image.length
val numCols = image(0).length
val flipped = Array.ofDim[Int](numRows, numCols)
for (row <- 0 until numRows) {
for (col <- 0 until numCols) {
flipped(row)(col) = image(row)(numCols - 1 - col)
}
}
flipped
}
}
Kotlin
class Solution {
fun rotateClockwise(image: Array<IntArray>): Array<IntArray> {
return flipOnVerticalAxis(transpose(image))
}
private fun transpose(image: Array<IntArray>): Array<IntArray> {
val size = image.size
val transposed = Array(size) { IntArray(size) }
for (row in 0 until size) {
for (col in 0 until size) {
transposed[row][col] = image[col][row]
}
}
return transposed
}
private fun flipOnVerticalAxis(image: Array<IntArray>): Array<IntArray> {
val numRows = image.size
val numCols = image[0].size
val flipped = Array(numRows) { IntArray(numCols) }
for (row in 0 until numRows) {
for (col in 0 until numCols) {
flipped[row][col] = image[row][numCols - 1 - col]
}
}
return flipped
}
}
Swift
class Solution {
func rotateClockwise(_ image: [[Int]]) -> [[Int]] {
return flipOnVerticalAxis(transpose(image))
}
private func transpose(_ image: [[Int]]) -> [[Int]] {
let size = image.count
var transposed = Array(repeating: Array(repeating: 0, count: size), count: size)
for row in 0..<size {
for col in 0..<size {
transposed[row][col] = image[col][row]
}
}
return transposed
}
private func flipOnVerticalAxis(_ image: [[Int]]) -> [[Int]] {
let numRows = image.count
let numCols = image[0].count
var flipped = Array(repeating: Array(repeating: 0, count: numCols), count: numRows)
for row in 0..<numRows {
for col in 0..<numCols {
flipped[row][col] = image[row][numCols - 1 - col]
}
}
return flipped
}
}
C#
public class Solution {
public int[][] RotateClockwise(int[][] image) {
return FlipOnVerticalAxis(Transpose(image));
}
private int[][] Transpose(int[][] image) {
int size = image.Length;
var transposed = new int[size][];
for (int row = 0; row < size; row++) {
transposed[row] = new int[size];
for (int col = 0; col < size; col++) {
transposed[row][col] = image[col][row];
}
}
return transposed;
}
private int[][] FlipOnVerticalAxis(int[][] image) {
int numRows = image.Length, numCols = image[0].Length;
var flipped = new int[numRows][];
for (int row = 0; row < numRows; row++) {
flipped[row] = new int[numCols];
for (int col = 0; col < numCols; col++) {
flipped[row][col] = image[row][numCols - 1 - col];
}
}
return flipped;
}
}
Dart
class Solution {
List<List<int>> rotateClockwise(List<List<int>> image) {
return flipOnVerticalAxis(transpose(image));
}
List<List<int>> transpose(List<List<int>> image) {
int size = image.length;
List<List<int>> transposed = List.generate(size, (_) => List.filled(size, 0));
for (int row = 0; row < size; row++) {
for (int col = 0; col < size; col++) {
transposed[row][col] = image[col][row];
}
}
return transposed;
}
List<List<int>> flipOnVerticalAxis(List<List<int>> image) {
int numRows = image.length;
int numCols = image[0].length;
List<List<int>> flipped = List.generate(numRows, (_) => List.filled(numCols, 0));
for (int row = 0; row < numRows; row++) {
for (int col = 0; col < numCols; col++) {
flipped[row][col] = image[row][numCols - 1 - col];
}
}
return flipped;
}
}
Ruby
class Solution
def rotate_clockwise(image)
flip_on_vertical_axis(transpose(image))
end
def transpose(image)
size = image.length
transposed = Array.new(size) { Array.new(size, 0) }
size.times do |row|
size.times do |col|
transposed[row][col] = image[col][row]
end
end
transposed
end
def flip_on_vertical_axis(image)
num_rows = image.length
num_cols = image[0].length
flipped = Array.new(num_rows) { Array.new(num_cols, 0) }
num_rows.times do |row|
num_cols.times do |col|
flipped[row][col] = image[row][num_cols - 1 - col]
end
end
flipped
end
end
Rust
impl Solution {
pub fn rotate_clockwise(&self, image: Vec<Vec<i32>>) -> Vec<Vec<i32>> {
Self::flip_on_vertical_axis(&Self::transpose(&image))
}
fn transpose(image: &Vec<Vec<i32>>) -> Vec<Vec<i32>> {
let size = image.len();
let mut transposed = vec![vec![0; size]; size];
for row in 0..size {
for col in 0..size {
transposed[row][col] = image[col][row];
}
}
transposed
}
fn flip_on_vertical_axis(image: &Vec<Vec<i32>>) -> Vec<Vec<i32>> {
let num_rows = image.len();
let num_cols = image[0].len();
let mut flipped = vec![vec![0; num_cols]; num_rows];
for row in 0..num_rows {
for col in 0..num_cols {
flipped[row][col] = image[row][num_cols - 1 - col];
}
}
flipped
}
}
PHP
class Solution {
public function rotateClockwise(array $image): array {
return $this->flipOnVerticalAxis($this->transpose($image));
}
private function transpose(array $image): array {
$size = count($image);
$transposed = array_fill(0, $size, array_fill(0, $size, 0));
for ($row = 0; $row < $size; $row++) {
for ($col = 0; $col < $size; $col++) {
$transposed[$row][$col] = $image[$col][$row];
}
}
return $transposed;
}
private function flipOnVerticalAxis(array $image): array {
$numRows = count($image);
$numCols = count($image[0]);
$flipped = array_fill(0, $numRows, array_fill(0, $numCols, 0));
for ($row = 0; $row < $numRows; $row++) {
for ($col = 0; $col < $numCols; $col++) {
$flipped[$row][$col] = $image[$row][$numCols - 1 - $col];
}
}
return $flipped;
}
}
Key Takeaways
- Clockwise 90-degree rotation equals transpose followed by vertical flip. This decomposition is far easier to implement correctly than deriving the direct index mapping.
- Both operations iterate over every cell once, giving O(n^2) time for an n-by-n matrix.
- The approach creates a new output matrix using O(n^2) space. For in-place rotation, transpose the upper triangle in-place and then reverse each row.
- Understanding this decomposition unlocks counterclockwise rotation (transpose then horizontal flip), 180-degree rotation (two 90-degree rotations), and other matrix transformations.
- Always trace through a small example (2x2 or 3x3) to verify your index mapping before scaling up.
Related Problems
Once you're comfortable rotating matrices, try these related problems to strengthen your matrix manipulation skills:
- Return the transpose of a square image (the first half of this problem's solution)
- Rotate a square image counterclockwise (transpose then flip horizontally instead)
- Spiral matrix traversal (another popular matrix traversal problem)
This problem and hundreds of other matrix and array challenges are available on Firecode, where over 50,000 engineers have sharpened their skills for technical interviews. Whether you're targeting Amazon, Microsoft, or any top tech company, building strong 2D array intuition will pay dividends across your interview preparation.