Flip an image on its horizontal axis
You're working through a Bloomberg phone screen when the interviewer pulls up a shared editor and says, "Imagine you have a digital photo stored as a grid of pixel values. How would you flip it upside down?" The question sounds deceptively simple, but it tests your comfort with 2D array indexing and your ability to write clean, correct code under pressure. This problem, also known as "Flip an Image Vertically" on other platforms, is a staple warm-up question at companies like Bloomberg.
TL;DR
Reverse the order of rows in a 2D array to flip the image on its horizontal axis. For each index i from 0 to image.length - 1, place image[image.length - 1 - i] into position i of a new result array. This runs in O(m * n) time and O(m * n) space, where m is the number of rows and n the number of columns. In Java, you can express this concisely with IntStream.range().mapToObj().toArray().
Why This Problem Matters
Matrix manipulation problems are everywhere in technical interviews, and this one is a perfect entry point. It builds the mental model for working with 2D arrays, index arithmetic, and the distinction between mutating data in-place versus returning a new copy. Once you're comfortable flipping rows, you'll find that related problems like matrix rotation, transposition, and spiral traversal become much more approachable.
Understanding the Problem
We're given a black and white image represented as a 2D array of integers (values between 0 and 255, where 0 is black and 255 is white). Our job is to flip the image on its horizontal axis, which means turning it upside down. The top row becomes the bottom row, and the bottom row becomes the top.
Here's a concrete example. Given:
flipImage([[255,0,0],[0,0,255]]) -> [[0,0,255],[255,0,0]]
The first row [255, 0, 0] moves to the bottom, and the second row [0, 0, 255] moves to the top.
Let's visualize a slightly larger example. Consider the grayscale image [[255, 0, 0], [0, 0, 255]]:
Original image:
Loading visualization...
After flipping on the horizontal axis:
Loading visualization...
Notice that each row keeps its internal values intact. Only the ordering of rows changes. This is the key distinction between a horizontal flip (rows reversed) and a vertical flip (columns reversed within each row).
Edge Cases to Consider
- Empty image (zero rows): Return an empty array
- Single row: The flipped version is identical to the input
- Empty rows (like
[[]]): Return the same structure - Square vs. rectangular: The algorithm works the same regardless of dimensions
Solution Approach
The core idea is straightforward: build a new 2D array where row i of the result holds row image.length - 1 - i of the original.
The Index Mapping
For an image with 3 rows, the mapping looks like this:
Loading visualization...
Row 0 of the original becomes row 2 of the result. Row 1 stays in place. Row 2 moves to position 0. The general formula is:
result[i] = image[image.length - 1 - i]
This works for any number of rows. When the image has an odd number of rows, the middle row maps to itself.
Walking Through a 3x3 Example
Let's trace through [[1,2,3],[4,5,6],[7,8,9]]:
Original:
Loading visualization...
We create a new array and fill it using our index mapping:
result[0] = image[2]which is[7, 8, 9]result[1] = image[1]which is[4, 5, 6]result[2] = image[0]which is[1, 2, 3]
Flipped:
Loading visualization...
The image is now upside down. Rows that were at the top are now at the bottom.
Implementation
Prefer a different language? Jump to solutions in other languages.
Here's the Java solution using streams for a clean, functional approach:
import java.util.stream.IntStream;
public class Solution {
public int[][] flipImage(int[][] image) {
// Create a stream of indices from 0 to image.length - 1
// For each index i, grab the row at position image.length - 1 - i
// Collect all rows into a new 2D array
return IntStream
.range(0, image.length)
.mapToObj(i -> image[image.length - 1 - i])
.toArray(int[][]::new);
}
}
Let's break down what's happening:
IntStream.range(0, image.length)generates indices[0, 1, 2, ..., n-1].mapToObj(i -> image[image.length - 1 - i])maps each index to its corresponding row from the end.toArray(int[][]::new)collects the mapped rows into a newint[][]
This functional approach avoids explicit loops and temporary variables, making the intent clear in a single expression.
The Imperative Alternative
If you prefer a traditional loop-based approach, or your interviewer asks you to avoid streams:
public class Solution {
public int[][] flipImage(int[][] image) {
int rows = image.length;
int[][] result = new int[rows][];
for (int i = 0; i < rows; i++) {
result[i] = image[rows - 1 - i];
}
return result;
}
}
Both approaches produce the same result. The loop version is arguably easier to trace through during a whiteboard session, while the stream version is more idiomatic modern Java.
Complexity Analysis
Time Complexity: O(m * n)
We iterate through every row and copy a reference to it in the new array. The row reference copy itself is O(1), but since we're producing a result that contains all m * n elements, the overall complexity is O(m * n). If you needed to deep-copy each row (creating entirely independent arrays), you'd pay O(n) per row for the copy, but the total is still O(m * n).
Space Complexity: O(m * n)
We allocate a new 2D array to hold the flipped result. This uses O(m * n) space for the result array. Note that our Java solution shares row references between the original and result arrays rather than deep-copying. If immutability is a concern, you'd deep-copy each row, but the asymptotic space stays the same.
Could We Do Better on Space?
If the problem allowed in-place modification, we could swap rows from the top and bottom, working inward. That would reduce extra space to O(1). But the problem explicitly asks us to return a new copy without modifying the input, so O(m * n) space is the best we can achieve.
Common Pitfalls
-
Confusing horizontal and vertical flips: A horizontal flip reverses row order. A vertical flip reverses column order within each row. Read the problem statement carefully.
-
Modifying the original array: The problem says to return a copy. If you swap rows in-place, you've violated this constraint.
-
Off-by-one in the index formula: The last valid index is
image.length - 1, notimage.length. Getting this wrong produces anArrayIndexOutOfBoundsException. -
Forgetting edge cases: An empty input array should return an empty array, not throw a null pointer exception. Test with
[[]]as well.
Interview Tips
When this problem comes up in an interview:
-
Clarify what "horizontal axis" means: Ask whether it means reversing rows (upside down) or reversing columns (mirror). Most interviewers mean row reversal, but it's worth confirming.
-
Ask about in-place vs. copy: The problem says "return a copy," but confirming this shows attention to detail.
-
Start with the formula: Write
result[i] = image[n - 1 - i]on the whiteboard before coding. It makes the implementation almost trivial. -
Mention the in-place alternative: Even though the problem doesn't require it, briefly noting that you could swap rows in O(1) space shows deeper understanding.
-
Consider follow-ups: The interviewer might ask you to flip vertically, rotate 90 degrees, or transpose the matrix. Having the 2D index arithmetic fresh in your mind makes these extensions natural.
Key Takeaways
- Flipping an image on its horizontal axis is equivalent to reversing the order of rows in a 2D array. The formula
result[i] = image[n - 1 - i]captures the entire logic. - Java's
IntStreamprovides a clean functional way to express this transformation without explicit loops. - Time and space are both O(m * n) because you visit every element and produce a full copy. In-place row swapping can reduce space to O(1) if the problem allows mutation.
- This problem is a building block for harder matrix operations like 90-degree rotation (transpose + flip) and spiral traversal.
- Always confirm whether "flip" means row reversal or column reversal, and whether you should modify in-place or return a new array.
Solutions in Other Languages
Python
Python makes this particularly concise with slice notation:
from typing import List
class Solution:
def flip_image(self, image: List[List[int]]) -> List[List[int]]:
return image[::-1]
The [::-1] slice creates a shallow copy of the list with elements in reverse order. Since each element is a row (a list), this reverses the row ordering.
JavaScript
class Solution {
flipImage(image) {
return image.map((row, i) => image[image.length - 1 - i]);
}
}
The map callback ignores the current row and instead picks the row from the opposite end of the array.
TypeScript
class Solution {
flipImage(image: number[][]): number[][] {
return image.map((_, i) => image[image.length - 1 - i]);
}
}
Same approach as JavaScript with type annotations added.
C++
#include <vector>
class Solution {
public:
std::vector<std::vector<int>> flipImage(std::vector<std::vector<int>> image) {
if (image.empty()) return {};
auto rows = image.size();
std::vector<std::vector<int>> reversedImage;
reversedImage.resize(rows, std::vector<int>(image[0].size()));
for (auto i = 0; i < rows; i++) {
reversedImage[rows - 1 - i] = image[i];
}
return reversedImage;
}
};
Go
func FlipImage(image [][]int) [][]int {
rows := len(image)
reversedImage := make([][]int, rows)
for i, row := range image {
reversedImage[rows-1-i] = row
}
return reversedImage
}
Scala
class Solution {
def flipImage(image: Array[Array[Int]]): Array[Array[Int]] = {
image.indices.map { i =>
image(image.length - 1 - i)
}.toArray
}
}
Kotlin
class Solution {
fun flipImage(image: Array<IntArray>): Array<IntArray> {
return Array(image.size) { i -> image[image.size - 1 - i] }
}
}
Swift
class Solution {
func flipImage(_ image: [[Int]]) -> [[Int]] {
return (0..<image.count).map { i in image[image.count - 1 - i] }
}
}
Ruby
class Solution
def flip_image(image)
image.reverse
end
end
Ruby's reverse method returns a new array with elements in reverse order.
Rust
pub fn flip_image(image: Vec<Vec<i32>>) -> Vec<Vec<i32>> {
(0..image.len())
.map(|i| image[image.len() - 1 - i].clone())
.collect()
}
C#
using System.Linq;
public class Solution {
public int[][] FlipImage(int[][] image) {
return Enumerable
.Range(0, image.Length)
.Select(i => image[image.Length - 1 - i])
.ToArray();
}
}
Dart
class Solution {
List<List<int>> flipImage(List<List<int>> image) {
return List.generate(image.length, (i) => image[image.length - 1 - i]);
}
}
PHP
class Solution {
public function flipImage(array $image): array {
return array_reverse($image);
}
}
Practice Makes Perfect
Once you've nailed flipping on the horizontal axis, try these related matrix problems to strengthen your 2D array skills:
- Rotate an image by 90 degrees (combine transpose with flip)
- Spiral order traversal of a matrix
- Zero out rows and columns containing zeros
- Transpose a matrix
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're just getting started or aiming for your dream role, mastering fundamentals like 2D array manipulation will set you up for success.