2D Arrays
What Is a 2D Array?
A 2D array is an array of arrays. Where a 1D array stores a sequence of elements accessed by a single index, a 2D array stores a grid of elements accessed by two indices — a row and a column.
Think of a spreadsheet. Row 2, column 3 is a specific cell. Change either coordinate and you get a completely different cell. That two-coordinate addressing is exactly how 2D arrays work.
2D array with 3 rows and 4 columns:
col 0 col 1 col 2 col 3
row 0 [ 10, 20, 30, 40 ]
row 1 [ 50, 60, 70, 80 ]
row 2 [ 90, 100, 110, 120 ]
Access: arr[row][col]
arr[0][0] = 10 arr[1][2] = 70 arr[2][3] = 120
The most common use case is a matrix — a rectangular grid where every row has the same number of columns. Most interview problems involving grids, game boards, images, and adjacency matrices use 2D arrays.
Declaration and Initialization
Every language has its own syntax. The concept is the same — a grid with rows and columns, accessed as arr[row][col].
Declare with Fixed Size
1public class Declare2DArray {
2
3 public static void main(String[] args) {
4 // Declare a 3x4 integer matrix — all values default to 0
5 int[][] matrix = new int[3][4];
6
7 System.out.println("Rows: " + matrix.length); // 3
8 System.out.println("Columns: " + matrix[0].length); // 4
9 System.out.println("Default value: " + matrix[1][2]); // 0
10
11 // Declare a 2D boolean grid — all values default to false
12 boolean[][] grid = new boolean[5][5];
13 System.out.println("Grid default: " + grid[0][0]); // false
14 }
15}Output:
Rows: 3
Columns: 4
Default: 0
Initialize with Known Values
1public class Init2DArray {
2
3 public static void main(String[] args) {
4 // Initialize with literal values — 3 rows, 3 columns
5 int[][] matrix = {
6 {1, 2, 3},
7 {4, 5, 6},
8 {7, 8, 9}
9 };
10
11 System.out.println("Top-left: " + matrix[0][0]); // 1
12 System.out.println("Center: " + matrix[1][1]); // 5
13 System.out.println("Bottom-right: " + matrix[2][2]); // 9
14 }
15}Output:
Top-left: 1
Center: 5
Bottom-right: 9
Memory Layout: Row-Major Order
In most languages, 2D arrays are stored in row-major order — all elements of row 0 come first, then all of row 1, then row 2.
Matrix: [1, 2, 3] [4, 5, 6] [7, 8, 9] Memory (row-major): Address: 1000 1004 1008 1012 1016 1020 1024 1028 1032 Value: 1 2 3 4 5 6 7 8 9 Position: [0][0][0][1][0][2][1][0][1][1][1][2][2][0][2][1][2][2]
The address formula for element [row][col] in a matrix with numCols columns:
address = base + (row × numCols + col) × element_size
This means row traversal (accessing arr[i][0], arr[i][1], arr[i][2]...) is cache-friendly — elements are adjacent in memory. Column traversal (accessing arr[0][j], arr[1][j], arr[2][j]...) jumps by numCols elements between each access — more cache misses.
For performance-critical code on large matrices, prefer row traversal over column traversal.
Traversal 1: Full Row-by-Row Traversal
The most common pattern — visit every element, moving left to right within each row, top to bottom across rows.
1public class RowByRow {
2
3 public static void printMatrix(int[][] matrix) {
4 int rows = matrix.length;
5 int cols = matrix[0].length;
6
7 for (int r = 0; r < rows; r++) {
8 for (int c = 0; c < cols; c++) {
9 System.out.printf("%4d", matrix[r][c]);
10 }
11 System.out.println();
12 }
13 }
14
15 // Find the maximum element in the entire matrix
16 public static int findMax(int[][] matrix) {
17 int max = matrix[0][0];
18
19 for (int r = 0; r < matrix.length; r++) {
20 for (int c = 0; c < matrix[r].length; c++) {
21 if (matrix[r][c] > max) {
22 max = matrix[r][c];
23 }
24 }
25 }
26
27 return max;
28 }
29
30 public static void main(String[] args) {
31 int[][] matrix = {
32 {3, 7, 2, 8},
33 {1, 9, 5, 4},
34 {6, 0, 11, 3}
35 };
36
37 System.out.println("Matrix:");
38 printMatrix(matrix);
39 System.out.println("Maximum: " + findMax(matrix));
40 }
41}Output:
Matrix:
3 7 2 8
1 9 5 4
6 0 11 3
Maximum: 11
Traversal 2: Column-by-Column Traversal
Traverse each column top to bottom before moving to the next column. The outer loop controls the column, the inner loop moves down the rows.
1public class ColumnByColumn {
2
3 // Sum each column and return the column sums
4 public static int[] columnSums(int[][] matrix) {
5 int rows = matrix.length;
6 int cols = matrix[0].length;
7 int[] sums = new int[cols];
8
9 // Outer loop: column
10 for (int c = 0; c < cols; c++) {
11 // Inner loop: row — traverses down one column
12 for (int r = 0; r < rows; r++) {
13 sums[c] += matrix[r][c];
14 }
15 }
16
17 return sums;
18 }
19
20 public static void main(String[] args) {
21 int[][] matrix = {
22 {1, 2, 3},
23 {4, 5, 6},
24 {7, 8, 9}
25 };
26
27 int[] sums = columnSums(matrix);
28
29 System.out.print("Column sums: ");
30 for (int s : sums) System.out.print(s + " ");
31 System.out.println();
32 // col 0: 1+4+7=12, col 1: 2+5+8=15, col 2: 3+6+9=18
33 }
34}Output:
Column sums: 12 15 18
Traversal 3: Diagonal Traversal
Main Diagonal (top-left to bottom-right)
Elements where row == col. For an n×n matrix, there are n diagonal elements.
Anti-Diagonal (top-right to bottom-left)
Elements where row + col == n - 1. For an n×n matrix, there are also n anti-diagonal elements.
1public class DiagonalTraversal {
2
3 public static void main(String[] args) {
4 int[][] matrix = {
5 {1, 2, 3, 4},
6 {5, 6, 7, 8},
7 {9, 10, 11, 12},
8 {13, 14, 15, 16}
9 };
10
11 int n = matrix.length;
12
13 // Main diagonal: row == col → elements: 1, 6, 11, 16
14 System.out.print("Main diagonal: ");
15 for (int i = 0; i < n; i++) {
16 System.out.print(matrix[i][i] + " ");
17 }
18 System.out.println();
19
20 // Anti-diagonal: row + col == n-1 → elements: 4, 7, 10, 13
21 System.out.print("Anti-diagonal: ");
22 for (int i = 0; i < n; i++) {
23 System.out.print(matrix[i][n - 1 - i] + " ");
24 }
25 System.out.println();
26
27 // Sum of main diagonal
28 int diagSum = 0;
29 for (int i = 0; i < n; i++) diagSum += matrix[i][i];
30 System.out.println("Main diagonal sum: " + diagSum);
31 }
32}Output:
Main diagonal: 1 6 11 16
Anti-diagonal: 4 7 10 13
Main diagonal sum: 34
Matrix Transpose
The transpose of a matrix swaps rows and columns: element [r][c] moves to [c][r]. For an n×n matrix this can be done in-place; for an m×n matrix you need a new array.
1public class Transpose {
2
3 // In-place transpose of a square matrix — O(n²) time, O(1) space
4 public static void transposeInPlace(int[][] matrix) {
5 int n = matrix.length;
6
7 // Only swap above the diagonal (where r < c)
8 // Swapping both would double-swap and return to original
9 for (int r = 0; r < n; r++) {
10 for (int c = r + 1; c < n; c++) {
11 int temp = matrix[r][c];
12 matrix[r][c] = matrix[c][r];
13 matrix[c][r] = temp;
14 }
15 }
16 }
17
18 public static void printMatrix(int[][] m) {
19 for (int[] row : m) {
20 for (int x : row) System.out.printf("%4d", x);
21 System.out.println();
22 }
23 }
24
25 public static void main(String[] args) {
26 int[][] matrix = {
27 {1, 2, 3},
28 {4, 5, 6},
29 {7, 8, 9}
30 };
31
32 System.out.println("Original:");
33 printMatrix(matrix);
34
35 transposeInPlace(matrix);
36
37 System.out.println("Transposed:");
38 printMatrix(matrix);
39 }
40}Output:
Original:
1 2 3
4 5 6
7 8 9
Transposed:
1 4 7
2 5 8
3 6 9
Dry Run: Transpose of 3×3 Matrix
Original: [1, 2, 3] [4, 5, 6] [7, 8, 9] Swaps (only upper triangle, where c > r): r=0, c=1: swap [0][1]=2 and [1][0]=4 → row0=[1,4,3], row1=[2,5,6] r=0, c=2: swap [0][2]=3 and [2][0]=7 → row0=[1,4,7], row2=[3,8,9] r=1, c=2: swap [1][2]=6 and [2][1]=8 → row1=[2,5,8], row2=[3,6,9] Result: [1, 4, 7] [2, 5, 8] [3, 6, 9] Why only upper triangle? If we swapped both [r][c] and [c][r], each pair would be swapped twice — returning to the original. Swapping only where c > r ensures each pair is swapped exactly once.
Matrix Rotation (90 Degrees Clockwise)
Rotating a matrix 90 degrees clockwise in-place is a classic interview problem. The trick: transpose first, then reverse each row.
1import java.util.Arrays;
2
3public class RotateMatrix {
4
5 // Rotate 90 degrees clockwise in-place: transpose then reverse each row
6 public static void rotate90(int[][] matrix) {
7 int n = matrix.length;
8
9 // Step 1: Transpose — swap [r][c] with [c][r]
10 for (int r = 0; r < n; r++) {
11 for (int c = r + 1; c < n; c++) {
12 int temp = matrix[r][c];
13 matrix[r][c] = matrix[c][r];
14 matrix[c][r] = temp;
15 }
16 }
17
18 // Step 2: Reverse each row
19 for (int r = 0; r < n; r++) {
20 int left = 0, right = n - 1;
21 while (left < right) {
22 int temp = matrix[r][left];
23 matrix[r][left] = matrix[r][right];
24 matrix[r][right] = temp;
25 left++;
26 right--;
27 }
28 }
29 }
30
31 public static void main(String[] args) {
32 int[][] matrix = {
33 {1, 2, 3},
34 {4, 5, 6},
35 {7, 8, 9}
36 };
37
38 System.out.println("Before rotation:");
39 for (int[] row : matrix) System.out.println(Arrays.toString(row));
40
41 rotate90(matrix);
42
43 System.out.println("After 90° clockwise rotation:");
44 for (int[] row : matrix) System.out.println(Arrays.toString(row));
45 }
46}Output:
Before rotation:
[1, 2, 3]
[4, 5, 6]
[7, 8, 9]
After 90° clockwise rotation:
[7, 4, 1]
[8, 5, 2]
[9, 6, 3]
Dry Run: 90° Clockwise Rotation
Original: After Transpose: After Reverse Each Row: 1 2 3 1 4 7 7 4 1 4 5 6 → 2 5 8 → 8 5 2 7 8 9 3 6 9 9 6 3 Why this works: Rotating 90° clockwise maps [r][c] → [c][n-1-r] Transposing maps [r][c] → [c][r] Reversing row maps [c][r] → [c][n-1-r] Combined: [r][c] → [c][n-1-r] ✓
Working with Jagged Arrays
A jagged array has rows of different lengths. This is different from a rectangular matrix. Java and JavaScript support true jagged arrays; Python naturally does too since each row is an independent list.
1public class JaggedArray {
2
3 public static void main(String[] args) {
4 // Jagged array — each row has different length
5 int[][] triangle = new int[4][];
6 triangle[0] = new int[]{1};
7 triangle[1] = new int[]{1, 2};
8 triangle[2] = new int[]{1, 2, 3};
9 triangle[3] = new int[]{1, 2, 3, 4};
10
11 System.out.println("Triangle (jagged array):");
12 for (int r = 0; r < triangle.length; r++) {
13 for (int c = 0; c < triangle[r].length; c++) { // Note: triangle[r].length varies
14 System.out.print(triangle[r][c] + " ");
15 }
16 System.out.println();
17 }
18
19 // Total elements
20 int total = 0;
21 for (int[] row : triangle) total += row.length;
22 System.out.println("Total elements: " + total);
23 }
24}Output:
Triangle (jagged array):
1
1 2
1 2 3
1 2 3 4
Total elements: 10
Complexity of 2D Array Operations
| Operation | Time | Space | Notes |
|---|---|---|---|
| Access element [r][c] | O(1) | O(1) | Direct address formula |
| Update element [r][c] | O(1) | O(1) | Same as access |
| Full traversal (m×n) | O(m×n) | O(1) | Visit every element once |
| Row traversal (one row) | O(n) | O(1) | n elements in one row |
| Column traversal (one col) | O(m) | O(1) | m elements in one column |
| Diagonal traversal | O(min(m,n)) | O(1) | min dimension elements |
| Transpose (n×n, in-place) | O(n²) | O(1) | n²/2 swaps |
| Rotate 90° (n×n, in-place) | O(n²) | O(1) | Transpose + reverse rows |
| Create n×n matrix | O(n²) | O(n²) | Allocate all cells |
| Copy m×n matrix | O(m×n) | O(m×n) | New allocation |
For an m×n matrix with m rows and n columns, "full traversal" is O(m×n) — not O(n²) unless m = n.
Common Mistakes Beginners Make
The Python row aliasing bug. Writing [[0]*cols]*rows creates one list and fills the outer list with references to it. Modifying any "row" modifies all of them. Always use [[0]*cols for _ in range(rows)] to create independent rows.
Confusing rows and columns in the loop bounds. matrix.length is the number of rows. matrix[0].length is the number of columns. Swapping them causes out-of-bounds access on non-square matrices. Always name variables rows and cols explicitly.
Double-swapping in transpose. If you swap both [r][c] with [c][r] in a full nested loop (not just where c > r), each pair is swapped twice and the matrix returns to its original state. Only swap where c > r (the upper triangle).
Accessing columns before checking if the matrix is empty. matrix[0].length assumes at least one row exists. Always guard with if (matrix.length == 0) return before using matrix[0].
Using == to compare 2D arrays in Java. arr1 == arr2 compares references, not contents. For deep equality comparison of arrays, use Arrays.deepEquals(arr1, arr2) in Java.
Interview Questions
Q: How do you find the sum of all elements on the main diagonal of an n×n matrix?
The main diagonal consists of elements where row index equals column index: matrix[0][0], matrix[1][1], ..., matrix[n-1][n-1]. Iterate with a single loop for i from 0 to n-1 and accumulate matrix[i][i]. Time: O(n). Space: O(1).
Q: How do you rotate a matrix 90 degrees clockwise in-place?
Two steps, both O(n²) time and O(1) space. First, transpose the matrix: swap matrix[r][c] with matrix[c][r] for all pairs where c > r. Second, reverse each row. The combination maps element [r][c] to position [c][n-1-r], which is the 90° clockwise mapping.
Q: Why is column-major traversal slower than row-major on large matrices?
Modern CPUs load memory in 64-byte cache lines. In row-major storage, consecutive array elements are adjacent in memory. Row traversal accesses them sequentially — each cache line load gives you 16 integers worth of data. Column traversal skips by numCols elements between accesses, causing a new cache line fetch at nearly every step. For a 1000×1000 matrix of integers, column traversal can be 10x slower in practice despite identical Big-O.
Q: How do you check if a matrix is symmetric (equal to its own transpose)?
A matrix is symmetric if matrix[r][c] == matrix[c][r] for all valid pairs. Only check the upper triangle (where c > r) — if any such pair does not match, the matrix is not symmetric. Time: O(n²). Space: O(1). Also verify the matrix is square first.
FAQs
What is the difference between a 2D array and a matrix?
A matrix typically implies a rectangular shape where every row has the same number of columns and all elements are numbers. A 2D array is the programming construct — it can be jagged (rows of different lengths) and elements can be any type. In most interview contexts, "matrix" and "2D array" are used interchangeably to mean a rectangular grid of integers.
How does Python store 2D arrays — are they truly 2D in memory?
Python lists of lists are not stored contiguously like C arrays. The outer list stores references (pointers) to inner list objects, which are scattered across the heap. There is no row-major memory layout in the C sense. This is why Python lists of lists are slower for numeric computation than NumPy arrays, which do use true contiguous row-major storage with BLAS-optimized operations.
What is the correct way to deep-copy a 2D array?
In Java: Arrays.stream(original).map(int[]::clone).toArray(int[][]::new) or nested loops. In Python: [row[:] for row in original] or copy.deepcopy(original). In C++: the copy constructor of vector<vector<int>> does a deep copy automatically. In JavaScript: matrix.map(row => [...row]) creates independent row copies. Simply assigning (copy = original) creates a shallow copy — modifications to nested elements affect both.
Can a 2D array have rows of length zero?
Yes — a row can be an empty array. But accessing matrix[r][0] on an empty row causes an out-of-bounds error. Always check matrix[r].length > 0 before accessing any element in row r, especially when working with jagged arrays from user input or generated data.
Quick Quiz
Question 1: A 2D array has 4 rows and 6 columns. How many elements are visited in a full traversal?
- ›A) 10 (4 + 6)
- ›B) 24 (4 × 6)
- ›C) 16 (4²)
- ›D) 36 (6²)
Answer: B) 24. Full traversal visits every element. For an m×n matrix with m=4 rows and n=6 columns, total elements = m × n = 4 × 6 = 24.
Question 2: In Python, what is wrong with grid = [[0] * 3] * 4?
- ›A) The syntax is invalid
- ›B) It creates 4 independent rows of zeros
- ›C) All 4 rows share the same list — modifying one row modifies all
- ›D) It only creates one row instead of four
Answer: C) All 4 rows share the same list. The * 4 operation copies the reference to the same inner list four times. Setting grid[0][0] = 1 changes grid[1][0], grid[2][0], and grid[3][0] as well. Use [[0]*3 for _ in range(4)] instead.
Question 3: What is the index pattern for the main diagonal of an n×n matrix?
- ›A) row + col == n
- ›B) row == col
- ›C) row + col == n - 1
- ›D) row == n - col
Answer: B) row == col. The main diagonal runs from top-left [0][0] to bottom-right [n-1][n-1]. At every diagonal element, the row and column indices are equal. The anti-diagonal (top-right to bottom-left) satisfies row + col == n - 1.
Question 4: Why does in-place matrix transposition only swap elements where column > row?
- ›A) Elements below the diagonal are already in place
- ›B) The lower triangle does not exist in a square matrix
- ›C) Swapping only the upper triangle ensures each pair is swapped exactly once
- ›D) The main diagonal elements swap with themselves
Answer: C) Swapping only the upper triangle ensures each pair is swapped exactly once. Element [r][c] and [c][r] are paired. If we iterated the full matrix and swapped both positions, we would swap each pair twice — returning to the original state. Restricting to c > r means each pair is touched exactly once.
Summary
A 2D array is a grid of elements accessed by row and column indices. In memory, elements are stored in row-major order — all elements of row 0 before row 1, enabling efficient sequential row access.
The key ideas to carry forward:
- ›Access is O(1):
address = base + (row × numCols + col) × elementSize - ›Full traversal is O(m×n) — two nested loops, one for row, one for column
- ›Row traversal is cache-friendly; column traversal is cache-unfriendly on large matrices
- ›Main diagonal: elements where
row == col; anti-diagonal:row + col == n - 1 - ›In-place transpose: swap upper triangle only (
c > r) — O(n²) time, O(1) space - ›90° clockwise rotation: transpose then reverse each row — O(n²) time, O(1) space
- ›Python aliasing bug: always use
[[0]*cols for _ in range(rows)], never[[0]*cols]*rows - ›Jagged arrays: rows of different lengths — check
matrix[r].lengthseparately per row
In the next topic, you will explore Dynamic Arrays — how arrays grow and shrink automatically, the resizing mechanism under the hood, and the true cost of operations on dynamic collections.