Java Multi-dimensional Arrays
Java Multi-dimensional Arrays
A one-dimensional array is a list — a row of values. A two-dimensional array is a table — rows and columns. A three-dimensional array is a cube — layers of tables. Multi-dimensional arrays in Java are simply arrays of arrays, and that mental model explains every operation you will ever perform on them.
When you see a seating chart, a cricket scorecard with batsmen across multiple innings, or a monthly attendance register with students and dates — you are looking at data that naturally lives in a two-dimensional structure. Multi-dimensional arrays are Java's way to represent and work with exactly that kind of data.
How 2D Arrays Work
A 2D array in Java is an array where each element is itself a 1D array. The outer array holds rows. Each row holds the actual values.
int[][] marks = new int[3][4]
| |
rows columns
Visualised as a grid:
col 0 col 1 col 2 col 3
row 0 [ 85 ] [ 90 ] [ 78 ] [ 92 ]
row 1 [ 76 ] [ 88 ] [ 95 ] [ 71 ]
row 2 [ 65 ] [ 83 ] [ 79 ] [ 97 ]
Accessing:
marks[0][0] = 85 (row 0, col 0)
marks[1][2] = 95 (row 1, col 2)
marks[2][3] = 97 (row 2, col 3)
marks.length = 3 (number of rows)
marks[0].length = 4 (number of columns in row 0)
Declaring, Creating, and Initialising 2D Arrays
// File: TwoDArrayBasics.java
import java.util.Arrays;
public class TwoDArrayBasics {
public static void main(String[] args) {
// Way 1 — declare size, fill later
// JVM fills all slots with 0 (for int)
int[][] grid = new int[3][4];
grid[0][0] = 1; grid[0][1] = 2; grid[0][2] = 3; grid[0][3] = 4;
grid[1][0] = 5; grid[1][1] = 6; grid[1][2] = 7; grid[1][3] = 8;
grid[2][0] = 9; grid[2][1] = 10; grid[2][2] = 11; grid[2][3] = 12;
System.out.println("Way 1 (fill later):");
System.out.println(" Rows : " + grid.length);
System.out.println(" Columns : " + grid[0].length);
System.out.println(" grid[1][2] = " + grid[1][2]);
// Way 2 — inline initialiser (most readable)
int[][] matrix = {
{1, 2, 3},
{4, 5, 6},
{7, 8, 9}
};
System.out.println("\nWay 2 (inline initialiser):");
System.out.println(" Row 0: " + Arrays.toString(matrix[0]));
System.out.println(" Row 1: " + Arrays.toString(matrix[1]));
System.out.println(" Row 2: " + Arrays.toString(matrix[2]));
// Way 3 — declare rows first, add columns separately
String[][] schedule = new String[5][];
schedule[0] = new String[]{"Math", "Science"};
schedule[1] = new String[]{"English", "Hindi", "PE"};
schedule[2] = new String[]{"Math", "Computer", "Art"};
schedule[3] = new String[]{"Science", "English"};
schedule[4] = new String[]{"Hindi", "Math", "Science", "PE"};
System.out.println("\nWay 3 (jagged — rows added separately):");
for (int i = 0; i < schedule.length; i++) {
System.out.println(" Day " + (i + 1) + ": " + Arrays.toString(schedule[i]));
}
}
}Output:
Way 1 (fill later):
Rows : 3
Columns : 4
grid[1][2] = 7
Way 2 (inline initialiser):
Row 0: [1, 2, 3]
Row 1: [4, 5, 6]
Row 2: [7, 8, 9]
Way 3 (jagged — rows added separately):
Day 1: [Math, Science]
Day 2: [English, Hindi, PE]
Day 3: [Math, Computer, Art]
Day 4: [Science, English]
Day 5: [Hindi, Math, Science, PE]
Way 3 creates a jagged array — a 2D array where rows have different lengths. The outer array has five slots, each pointing to a String[] of its own size. Java allows this because the outer array stores references to row arrays — each row array can be any length.
Traversing a 2D Array
The standard pattern is two nested loops — outer loop for rows, inner loop for columns.
// File: TwoDTraversalDemo.java
public class TwoDTraversalDemo {
public static void main(String[] args) {
int[][] scores = {
{72, 85, 90},
{88, 61, 78},
{95, 83, 70},
{65, 91, 77}
};
// Method 1 — nested for loop (use when index is needed)
System.out.println("Nested for loop (with indices):");
for (int row = 0; row < scores.length; row++) {
for (int col = 0; col < scores[row].length; col++) {
System.out.printf(" scores[%d][%d] = %d%n", row, col, scores[row][col]);
}
}
System.out.println();
// Method 2 — nested for-each (cleaner, no index management)
System.out.println("Nested for-each (values only):");
int total = 0;
for (int[] row : scores) {
for (int score : row) {
total += score;
System.out.print(score + " ");
}
System.out.println();
}
System.out.println("Total of all scores: " + total);
System.out.println();
// Method 3 — print each row using Arrays.toString()
System.out.println("Row-by-row print:");
for (int[] row : scores) {
System.out.println(" " + java.util.Arrays.toString(row));
}
}
}Output:
Nested for loop (with indices):
scores[0][0] = 72
scores[0][1] = 85
scores[0][2] = 90
scores[1][0] = 88
...
Nested for-each (values only):
72 85 90
88 61 78
95 83 70
65 91 77
Total of all scores: 1055
Row-by-row print:
[72, 85, 90]
[88, 61, 78]
[95, 83, 70]
[65, 91, 77]
scores[row].length is used in the inner loop — not scores[0].length — because jagged arrays can have different column counts per row. Using scores[row].length always works correctly for both regular and jagged arrays.
Matrix Operations — Addition and Transpose
Two-dimensional arrays map directly to matrices in mathematics. Matrix addition and transposition are the most common operations.
// File: MatrixOperations.java
import java.util.Arrays;
public class MatrixOperations {
// Print a 2D array as a formatted grid
public static void printMatrix(String label, int[][] m) {
System.out.println(label + ":");
for (int[] row : m) {
System.out.print(" [ ");
for (int val : row) System.out.printf("%4d ", val);
System.out.println("]");
}
System.out.println();
}
// Add two matrices of the same dimensions
public static int[][] addMatrices(int[][] a, int[][] b) {
int rows = a.length;
int cols = a[0].length;
int[][] result = new int[rows][cols];
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
result[i][j] = a[i][j] + b[i][j];
}
}
return result;
}
// Transpose: rows become columns, columns become rows
public static int[][] transpose(int[][] m) {
int rows = m.length;
int cols = m[0].length;
int[][] result = new int[cols][rows]; // note: dimensions swap
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
result[j][i] = m[i][j]; // swap indices
}
}
return result;
}
public static void main(String[] args) {
int[][] matA = {
{1, 2, 3},
{4, 5, 6}
};
int[][] matB = {
{7, 8, 9},
{10, 11, 12}
};
printMatrix("Matrix A", matA);
printMatrix("Matrix B", matB);
printMatrix("A + B", addMatrices(matA, matB));
printMatrix("Transpose of A", transpose(matA));
}
}Output:
Matrix A:
[ 1 2 3 ]
[ 4 5 6 ]
Matrix B:
[ 7 8 9 ]
[ 10 11 12 ]
A + B:
[ 8 10 12 ]
[ 14 16 18 ]
Transpose of A:
[ 1 4 ]
[ 2 5 ]
[ 3 6 ]
Transposing a 2×3 matrix gives a 3×2 matrix — rows and columns swap. This is why the result array is declared new int[cols][rows] — dimensions are reversed.
Jagged Arrays — When Rows Have Different Lengths
Regular 2D arrays have equal column counts in every row. Jagged arrays let each row have its own length — useful when the data itself is irregular.
// File: JaggedArrayDemo.java
public class JaggedArrayDemo {
public static void main(String[] args) {
// Pascal's Triangle — each row has one more element than the previous
int[][] pascal = new int[6][];
for (int row = 0; row < pascal.length; row++) {
pascal[row] = new int[row + 1]; // row 0 has 1 element, row 5 has 6
pascal[row][0] = 1;
pascal[row][row] = 1;
for (int col = 1; col < row; col++) {
pascal[row][col] = pascal[row - 1][col - 1] + pascal[row - 1][col];
}
}
System.out.println("Pascal's Triangle:");
for (int row = 0; row < pascal.length; row++) {
// Indent for visual triangle shape
System.out.print(" ".repeat((pascal.length - row - 1) * 2));
for (int val : pascal[row]) {
System.out.printf("%4d", val);
}
System.out.println();
}
System.out.println();
// Show that rows have different lengths
System.out.println("Row lengths:");
for (int i = 0; i < pascal.length; i++) {
System.out.println(" pascal[" + i + "].length = " + pascal[i].length);
}
}
}Output:
Pascal's Triangle:
1
1 1
1 2 1
1 3 3 1
1 4 6 4 1
1 5 10 10 5 1
Row lengths:
pascal[0].length = 1
pascal[1].length = 2
pascal[2].length = 3
pascal[3].length = 4
pascal[4].length = 5
pascal[5].length = 6
Pascal's Triangle is a textbook jagged array — each row has exactly one more element than the previous. A regular int[6][6] would work too but wastes the upper-right slots. Jagged arrays use memory only for what the data actually needs.
2D Array vs Jagged Array vs 3D Array — Comparison Table
| Aspect | Regular 2D Array | Jagged Array | 3D Array |
|---|---|---|---|
| Declaration | int[][] a = new int[3][4] | int[][] a = new int[3][] | int[][][] a = new int[2][3][4] |
| Column count | Same for every row | Can differ per row | Same per slice |
| Memory layout | Rectangular grid | Irregular — each row separate | Cube of values |
| Access syntax | a[row][col] | a[row][col] | a[layer][row][col] |
| Row length check | a[0].length works for all | Must use a[row].length | a[0][0].length |
| When to use | Tables, matrices, grids | Variable-length rows, triangles | 3D spatial data, time series grids |
| Memory efficiency | Fixed — may waste slots | Efficient — only allocates what is needed | Higher overhead |
| Common use case | Exam scores, seating chart | Pascal's Triangle, timetable | RGB images, 3D game maps |
Basic Example
public class Main {
public static void main(String[] args) {
// A 2D array representing a 3x3 grid of exam scores
int[][] scores = {
{85, 90, 78},
{92, 88, 95},
{70, 65, 80}
};
int total = 0;
for (int row = 0; row < scores.length; row++) {
for (int col = 0; col < scores[row].length; col++) {
System.out.print(scores[row][col] + " ");
total += scores[row][col];
}
System.out.println();
}
System.out.println("Total: " + total);
}
}Output
85 90 78 92 88 95 70 65 80 Total: 743
The outer loop walks each row of the grid, and the inner loop walks each column within that row — this is the standard nested-loop pattern for visiting every cell of a 2D array exactly once. scores[row][col] gives the value at a specific row and column, and each value is added to total as it is printed, so by the end of the loops total holds the sum of every score in the grid. This same row-and-column shape is what a seating chart, a scorecard, or an attendance sheet naturally maps to.
3D Arrays — A Brief Introduction
A 3D array adds one more dimension — think of it as a stack of 2D grids. int[layers][rows][cols] is the shape.
// File: ThreeDArrayDemo.java
public class ThreeDArrayDemo {
public static void main(String[] args) {
// Monthly sales data:
// Dimension 1 — months (3 months: Jan, Feb, Mar)
// Dimension 2 — products (3 products)
// Dimension 3 — weeks (4 weeks per month)
// Value = units sold
int[][][] salesData = {
// January — 3 products x 4 weeks
{{120, 135, 110, 145}, {80, 90, 75, 95}, {200, 180, 210, 220}},
// February
{{130, 140, 125, 150}, {85, 88, 80, 92}, {190, 195, 205, 215}},
// March
{{150, 160, 145, 170}, {90, 95, 88, 100},{220, 230, 225, 240}}
};
String[] months = {"January", "February", "March"};
String[] products = {"Product A", "Product B", "Product C"};
System.out.println("Monthly Sales Summary (units):\n");
for (int m = 0; m < salesData.length; m++) {
System.out.println("=== " + months[m] + " ===");
for (int p = 0; p < salesData[m].length; p++) {
int monthTotal = 0;
for (int w = 0; w < salesData[m][p].length; w++) {
monthTotal += salesData[m][p][w];
}
System.out.printf(" %-12s: %d units/month%n",
products[p], monthTotal);
}
System.out.println();
}
// Grand total across all dimensions
int grandTotal = 0;
for (int[][] month : salesData) {
for (int[] product : month) {
for (int units : product) {
grandTotal += units;
}
}
}
System.out.println("Grand total across 3 months: " + grandTotal + " units");
}
}Output:
Monthly Sales Summary (units):
=== January ===
Product A : 510 units/month
Product B : 340 units/month
Product C : 810 units/month
=== February ===
Product A : 545 units/month
Product B : 345 units/month
Product C : 805 units/month
=== March ===
Product A : 625 units/month
Product B : 373 units/month
Product C : 915 units/month
Grand total across 3 months: 6268 units
Three nested for-each loops traverse a 3D array naturally — one per dimension. The shape is salesData[month][product][week]. 3D arrays are less common than 2D but appear in image processing (RGB channels), game grids (floor × row × column), and time-series data (month × category × week).
Best Practices
Use arr[row].length in the inner loop, not a hardcoded column count. For regular 2D arrays this always gives the right column count. For jagged arrays it is the only correct approach — each row can have a different number of columns. Using arr[0].length for all rows crashes on jagged arrays where later rows might be shorter.
Declare parallel 1D arrays alongside the 2D array rather than mixing concerns. STUDENT_NAMES[i] and ATTENDANCE[i] stay in sync by index without embedding names inside the 2D array. This separation keeps the data clean — names are metadata about rows, not part of the score data.
Use Arrays.deepToString() to print 2D arrays, not Arrays.toString(). Arrays.toString(marks) on a 2D array prints references — [[I@7852e922, ...]. Arrays.deepToString(marks) prints [[85, 90, 78], [76, 88, 95], ...]. This is the single most common printing mistake with multi-dimensional arrays.
Initialise 2D arrays close to their first use. Unlike 1D arrays, 2D array initialisers get long quickly. Declare, initialise, and use the array in the same block of code so the relationship between declaration and use stays visible.
Common Mistakes
Mistake 1 — Printing 2D Arrays Without deepToString()
int[][] marks = {{85, 90}, {78, 95}};
System.out.println(marks); // [[I@7852e922 — useless
System.out.println(Arrays.toString(marks)); // [[I@1b6d3586, [I@4554617c] — still useless
// Correct
System.out.println(Arrays.deepToString(marks)); // [[85, 90], [78, 95]]Mistake 2 — NullPointerException From Uninitialised Rows in Jagged Arrays
int[][] jagged = new int[3][]; // outer array allocated, rows are null
jagged[0] = new int[]{1, 2};
System.out.println(jagged[0][0]); // 1 — fine
System.out.println(jagged[1][0]); // NullPointerException — jagged[1] is null
// Fix: initialise every row before accessing itMistake 3 — Wrong Loop Condition for Inner Loop
int[][] grid = {{1, 2, 3}, {4, 5}}; // jagged — row 1 has only 2 elements
// Wrong — uses row 0's length for all rows
for (int i = 0; i < grid.length; i++) {
for (int j = 0; j < grid[0].length; j++) { // grid[0].length = 3 — wrong for row 1
System.out.println(grid[i][j]);
}
}
// Throws ArrayIndexOutOfBoundsException when i=1, j=2
// Correct — use grid[i].length
for (int i = 0; i < grid.length; i++) {
for (int j = 0; j < grid[i].length; j++) {
System.out.println(grid[i][j]);
}
}Mistake 4 — Confusing Row and Column Index Order
// The declaration int[rows][cols] means:
// First index = row, Second index = column
int[][] seats = new int[3][5]; // 3 rows, 5 columns per row
// Accessing the element at row 1, column 3:
seats[1][3] = 1; // CORRECT
seats[3][1] = 1; // ArrayIndexOutOfBoundsException — index 3 does not exist in 3-row arrayInterview Questions
Q1. What is a 2D array in Java and how does it work internally?
A 2D array in Java is an array of arrays. The outer array holds references to inner arrays — each inner array is a row. Declaring int[][] matrix = new int[3][4] creates an outer array of 3 slots, each pointing to a separate int[] of length 4. Because rows are independent objects, a jagged array — where each row has a different length — is valid. matrix.length gives the row count. matrix[i].length gives the column count for row i.
Q2. What is the difference between a regular 2D array and a jagged array?
A regular 2D array has the same number of columns in every row — declared as new int[rows][cols]. The JVM creates all rows with equal size. A jagged array is declared with only the row count — new int[rows][] — and each row is created separately with its own length. Rows can have different sizes. Pascal's Triangle, triangular matrices, and calendar data with variable days per month are natural uses for jagged arrays. Regular 2D arrays suit tables, grids, and matrices where every row has the same number of columns.
Q3. How do you traverse a 2D array in Java?
Two nested loops — outer for rows, inner for columns. Use for (int i = 0; i < arr.length; i++) for rows and for (int j = 0; j < arr[i].length; j++) for columns. Using arr[i].length in the inner loop rather than arr[0].length makes the traversal safe for jagged arrays. The nested for-each for (int[] row : arr) { for (int val : row) { ... } } is cleaner when index values are not needed.
Q4. What is Arrays.deepToString() and when do you need it?
Arrays.toString() works on 1D arrays — printing elements in [1, 2, 3] format. On a 2D array, it prints references to the inner arrays — [[I@7852e922, ...] — which is useless. Arrays.deepToString() recursively converts all levels to readable strings — [[85, 90], [78, 95]]. Always use Arrays.deepToString() when printing multi-dimensional arrays.
Q5. What exception is thrown when an invalid row or column index is used?
ArrayIndexOutOfBoundsException — the same exception as with 1D arrays. For a int[3][4] array, valid row indices are 0 to 2 and valid column indices are 0 to 3. Accessing arr[3][0] (row 3 does not exist) or arr[0][4] (column 4 does not exist) throws this exception. For jagged arrays, using arr[0].length as the inner loop limit for all rows throws the same exception when a later row is shorter.
Q6. How do you find the row sum and column sum of a 2D array?
Row sum: iterate each row with the inner loop, accumulate with a variable that resets before each row. Column sum: iterate with outer loop over columns and inner loop over rows — or accumulate a columnTotal[] array during normal row-first traversal. For a matrix m, rowSum[i] = sum of m[i][j] for all j and colSum[j] = sum of m[i][j] for all i. Both can be computed in a single pass of the standard nested loop.
FAQs
Can a 2D array in Java have rows of different lengths?
Yes — this is called a jagged array. Declare the outer array with only the row count (new int[rows][]) and initialise each row separately with its own new int[n]. The JVM treats each row as an independent 1D array. A jagged array is not a Java quirk — it is the natural result of 2D arrays being arrays of arrays.
Does Arrays.sort() work on 2D arrays?
Arrays.sort(arr) on a 2D array sorts the rows themselves — treating each inner array as a single element. This is rarely what you want. To sort by a specific column, you need a custom Comparator: Arrays.sort(arr, (row1, row2) -> row1[colIndex] - row2[colIndex]). To sort every row independently, loop over rows and call Arrays.sort(arr[i]) for each.
How much memory does a 2D array use?
A 2D array int[rows][cols] uses rows × cols × 4 bytes for the integer data (each int is 4 bytes), plus overhead for the outer array object and the rows inner array objects. For a int[1000][1000] array, integer data alone is 4 MB. The JVM also allocates 1001 array objects (one outer, 1000 inner). For very large matrices, memory layout and cache efficiency become important considerations.
Can you change the size of a 2D array after creation?
No. Like 1D arrays, a 2D array's dimensions are fixed at creation. However, in a jagged array, you can replace a row with a new array of a different length — arr[i] = new int[newLength]. This changes the size of that row but does not affect the outer array's row count. For fully dynamic 2D data, use ArrayList<ArrayList<Integer>>.
What is Arrays.deepEquals() and when do you use it?
Arrays.equals() compares 1D arrays element by element. For 2D arrays, it compares row references — two 2D arrays that hold equal values but are different objects return false. Arrays.deepEquals(a, b) recursively compares every element at every level — two 2D arrays with the same values in the same positions return true. Always use Arrays.deepEquals() when comparing multi-dimensional arrays for content equality.
Summary
Multi-dimensional arrays in Java are arrays of arrays. A 2D array is a grid — rows accessed by the first index, columns by the second. A jagged array lets rows have different lengths. A 3D array adds another dimension for data that naturally exists in three axes — months × products × weeks, layers × rows × columns.
The traversal pattern is always nested loops — one per dimension, using arr[i].length in the inner loop rather than a fixed column count. The most useful diagnostic tool is Arrays.deepToString() — print it before debugging and after any operation to see the full state of the array.
For interviews, be ready to traverse a 2D array and compute row sums and column sums simultaneously, explain what a jagged array is and how it differs from a regular 2D array, demonstrate the transpose operation, and explain why Arrays.toString() fails on 2D arrays.
What to Read Next
Use Java's built-in helper methods for working with arrays.
Practice Programs
Put this topic into practice — each program has full code, output and a step-by-step explanation you can run online.
Adds two matrices of the same size by summing the values at each matching row and column.
Flips a matrix over its main diagonal so that every row becomes a column.
Multiplies two matrices by pairing each row of the first with each column of the second.
Reads a matrix clockwise from the outside in, peeling off one ring of cells at a time.
Adds up the primary and secondary diagonals of a square matrix separately.
Generates and prints the first N rows of Pascal's triangle.
Fills an n x n grid with numbers 1 through n squared in clockwise spiral order.