Rotate a square image clockwise by 90 degrees

AS
Ackshaey Singh
Difficulty Hard
Time Complexity
O(m * n)
Space Complexity
O(m * n)
Matrix
Cisco Amazon Microsoft

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

  1. 1x1 matrix: Already rotated. Return it unchanged.
  2. 2x2 matrix: The simplest non-trivial case, good for tracing your algorithm.
  3. 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] = 1
  • transposed[0][1] = image[1][0] = 0
  • transposed[1][0] = image[0][1] = 0
  • transposed[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] = 0
  • flipped[0][1] = transposed[0][0] = 1
  • flipped[1][0] = transposed[1][1] = 0
  • flipped[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

  1. Getting the index mapping backwards: transposed[row][col] = image[col][row], not image[row][col]. Double-check by tracing a single element.

  2. Off-by-one in the flip: The reversed column index is numCols - 1 - col, not numCols - col. Missing the -1 will cause an ArrayIndexOutOfBoundsException.

  3. Confusing clockwise with counterclockwise: Clockwise is transpose then flip vertically. Counterclockwise is transpose then flip horizontally. Mixing these up produces the wrong rotation direction.

  4. Assuming square in flip: Although the problem guarantees a square image, writing flipOnVerticalAxis to handle rectangular matrices (using separate numRows and numCols) makes the helper more general and avoids bugs if requirements change.

Interview Tips

When solving this in an interview:

  1. 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.

  2. Mention the decomposition early. Saying "rotation equals transpose plus flip" shows the interviewer you see the elegant approach rather than brute-forcing index arithmetic.

  3. 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.

  4. 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.

Frequently Asked Questions

Can you rotate a matrix in-place without extra space?
Yes. The in-place approach transposes the matrix and then reverses each row, all within the original array. This achieves O(1) extra space instead of O(n*n). The trade-off is that you modify the input directly, which may not always be desirable.
What is the time complexity of rotating a matrix by 90 degrees?
The time complexity is O(m * n) where m and n are the matrix dimensions. Every element must be read once and written once, regardless of whether you use the transpose-then-flip approach or a direct index mapping. For a square matrix of size n, this simplifies to O(n^2).
What is the difference between clockwise and counterclockwise rotation?
Clockwise rotation by 90 degrees maps element [row][col] to [col][n-1-row]. This is equivalent to transposing then flipping on the vertical axis. Counterclockwise rotation maps element [row][col] to [n-1-col][row], which is equivalent to transposing then flipping on the horizontal axis.
Why decompose rotation into transpose plus flip?
Breaking rotation into two simpler operations makes the code easier to write, test, and reason about. Each sub-operation has a clear, well-understood index mapping. This decomposition also makes the approach reusable, since transpose and flip are useful building blocks for other matrix transformations.
How do you rotate a non-square (rectangular) matrix?
For an m-by-n rectangle, rotation produces an n-by-m result. You cannot do this in-place because the dimensions change. Allocate a new matrix of size n-by-m and map each element from [row][col] to [col][m-1-row] for clockwise rotation.
How do you rotate a matrix by 180 degrees?
Rotating by 180 degrees is equivalent to reversing the order of all rows and then reversing each row individually, or equivalently, applying two consecutive 90-degree rotations. The index mapping is [row][col] to [n-1-row][n-1-col].
How does this problem relate to image processing?
Digital images are stored as 2D pixel arrays. Rotating a photo on your phone applies exactly this matrix rotation to every color channel. The transpose-then-flip approach is used in real image processing libraries because it maps cleanly to memory access patterns and can be parallelized efficiently.
What is the matrix transpose operation?
The transpose of a matrix swaps its rows and columns. Element [row][col] moves to position [col][row]. For a square matrix, this mirrors the matrix along its main diagonal running from the top-left to the bottom-right corner.
How often is the rotate image problem asked in interviews?
Rotate Image is a popular matrix manipulation problem at companies like Amazon, Microsoft, and Cisco. It appears frequently in 2026 interviews because it tests understanding of 2D array indexing, in-place algorithms, and the ability to decompose a complex operation into simpler steps.
What edge cases should you handle for matrix rotation?
Handle a 1x1 matrix (return it unchanged), an empty matrix (return empty), and verify your solution works for both even and odd dimensions. The 1x1 case is the simplest base case and a good sanity check during implementation.