☕ Java

Two-Dimensional Array

A two-dimensional array in Java is an array of arrays — each element of the outer array is itself a one-dimensional array. It is used to represent tabular data with rows and columns: matrices, game boards, spreadsheet grids, pixel buffers, and any data that is naturally organised in two dimensions. In Java, a 2D array is declared with two sets of brackets and accessed with two indices: array[row][column].

How Two-Dimensional Arrays Work in Java

Java's two-dimensional arrays are implemented as arrays of arrays, not as a single contiguous block of memory. When you create a 2D array like new int[3][4], Java creates one outer array with 3 elements, and for each element, creates an inner array with 4 elements. The outer array holds references to the inner arrays. This is fundamentally different from 2D arrays in languages like C, where a 2D array is a single flat block of memory. The practical consequence of this implementation is that rows are independent objects on the heap. They do not need to have the same length — this is the basis of jagged arrays. The reference in row 0 of the outer array can point to an inner array of length 4, while the reference in row 1 can point to an inner array of length 6. Regular rectangular 2D arrays are simply the special case where all inner arrays happen to have the same length. Accessing element at row r, column c requires two dereferences: first read outer[r] to get the reference to the inner array, then read inner[c] to get the value. This double dereference means 2D arrays have slightly worse cache performance than a flattened 1D array would for the same data, because the inner arrays may be scattered in memory rather than contiguous. For performance-critical code with very large matrices, using a 1D array with manual index computation (index = row * numCols + col) avoids this and keeps the data cache-friendly.
Java
// ── 2D array memory model: ───────────────────────────────────────────
//
//  int[][] grid = new int[3][4];
//
//  Stack:               Heap (outer array):
//  grid ──────────────▶ [ref0] [ref1] [ref2]
//                          │       │       │
//                          ▼       ▼       ▼
//                       [0][0] [0][0] [0][0]   ← inner arrays (rows)
//                       [0][0] [0][0] [0][0]
//                       (4 ints each)
//
//  grid[1][2] requires:
//    1. Read grid[1]    → reference to row 1 array
//    2. Read row1[2]    → value at column 2

int[][] grid = new int[3][4];   // 3 rows, 4 columns

// ── Dimensions: ───────────────────────────────────────────────────────
System.out.println("Rows:    " + grid.length);     // 3 — outer array length
System.out.println("Columns: " + grid[0].length);  // 4 — inner array length
// grid.length gives rows; grid[0].length gives columns (of row 0)

// ── Initialiser syntax — rectangular 2D array: ────────────────────────
int[][] matrix = {
    {1, 2, 3},   // row 0
    {4, 5, 6},   // row 1
    {7, 8, 9}    // row 2
};

// ── Accessing elements: ───────────────────────────────────────────────
System.out.println(matrix[0][0]);  // 1   — row 0, col 0
System.out.println(matrix[1][2]);  // 6   — row 1, col 2
System.out.println(matrix[2][1]);  // 8   — row 2, col 1

// ── Modifying elements: ───────────────────────────────────────────────
matrix[1][1] = 99;
System.out.println(matrix[1][1]);  // 99

// ── rows are separate objects: ───────────────────────────────────────
int[] row0 = matrix[0];   // row0 points to the first inner array
row0[0] = 100;
System.out.println(matrix[0][0]);  // 100 — same object, changed through row0

Traversing a Two-Dimensional Array

Traversing a 2D array requires nested loops — an outer loop iterating over rows and an inner loop iterating over columns within each row. The standard traversal accesses elements in row-major order: all columns of row 0, then all columns of row 1, and so on. This order corresponds to how the inner arrays are laid out and is cache-friendly in terms of the inner array accesses. For the enhanced for-each loop, the outer loop produces each inner array (row), and the inner loop produces each element of that row. This form is clean and avoids index variables but cannot modify elements (for primitive arrays) and does not give access to the row or column indices. The nested loop pattern is also used for column-major traversal (outer loop over columns, inner loop over rows) and for operations that need access to adjacent elements (like checking neighbours in a game board). For matrix operations like transpose, both row and column indices are actively needed so the indexed for loop is required.
Java
// ── Nested for loop — standard row-major traversal: ──────────────────
int[][] scores = {
    {85, 90, 78, 92},  // student 0: 4 subjects
    {76, 88, 95, 83},  // student 1
    {91, 79, 84, 87}   // student 2
};

System.out.println("All scores:");
for (int row = 0; row < scores.length; row++) {
    for (int col = 0; col < scores[row].length; col++) {
        System.out.printf("%4d", scores[row][col]);
    }
    System.out.println();
}
//   85  90  78  92
//   76  88  95  83
//   91  79  84  87

// ── Enhanced for-each — clean reading without indices: ────────────────
System.out.println("For-each traversal:");
for (int[] row : scores) {
    for (int score : row) {
        System.out.printf("%4d", score);
    }
    System.out.println();
}

// ── Row sums and averages: ────────────────────────────────────────────
System.out.println("Student averages:");
for (int row = 0; row < scores.length; row++) {
    int rowSum = 0;
    for (int score : scores[row]) {
        rowSum += score;
    }
    double avg = (double) rowSum / scores[row].length;
    System.out.printf("  Student %d: %.1f%n", row, avg);
}

// ── Column sums: ──────────────────────────────────────────────────────
System.out.println("Subject totals:");
int numSubjects = scores[0].length;
for (int col = 0; col < numSubjects; col++) {
    int colSum = 0;
    for (int[] row : scores) {
        colSum += row[col];
    }
    System.out.printf("  Subject %d total: %d%n", col, colSum);
}

// ── Matrix transpose: ────────────────────────────────────────────────
int rows = scores.length, cols = scores[0].length;
int[][] transposed = new int[cols][rows];
for (int r = 0; r < rows; r++) {
    for (int c = 0; c < cols; c++) {
        transposed[c][r] = scores[r][c];
    }
}
System.out.println("Transposed:");
for (int[] row : transposed) {
    System.out.println(Arrays.toString(row));
}

Matrix Operations

Two-dimensional arrays are the natural representation for mathematical matrices. Matrix addition, subtraction, and multiplication are fundamental operations in many domains: computer graphics, machine learning, scientific computing, and game physics. Implementing these operations correctly with 2D arrays is an important exercise in nested loops and index manipulation. Matrix multiplication is the most complex of these operations — it is not element-wise. The element at position [i][j] in the result is the dot product of row i of the first matrix and column j of the second matrix. This requires three nested loops and the constraint that the number of columns in the first matrix must equal the number of rows in the second. The result matrix has dimensions [rows of first] × [cols of second]. Understanding matrix multiplication in code also demonstrates an important performance principle: the naive triple-nested loop has O(n³) complexity for n×n matrices. The order of the loops matters for cache performance — loop order ijk accesses the second matrix in column-major order (poor cache behaviour), while loop order ikj accesses it in row-major order (much better cache behaviour). This is a classic example of how memory access patterns affect real-world performance independently of algorithmic complexity.
Java
// ── Matrix addition — element-wise, same dimensions required: ────────
public static int[][] matrixAdd(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;
}

int[][] m1 = {{1, 2, 3}, {4, 5, 6}};
int[][] m2 = {{7, 8, 9}, {1, 2, 3}};
int[][] sum = matrixAdd(m1, m2);
// [[8, 10, 12], [5, 7, 9]]

// ── Matrix multiplication: ────────────────────────────────────────────
// Result[i][j] = sum of a[i][k] * b[k][j] for all k
// Requirement: a.cols == b.rows
// Result dimensions: a.rows × b.cols
public static int[][] matrixMultiply(int[][] a, int[][] b) {
    int aRows = a.length;
    int aCols = a[0].length;
    int bCols = b[0].length;

    if (aCols != b.length)
        throw new IllegalArgumentException(
            "Incompatible dimensions: " + aCols + " != " + b.length);

    int[][] result = new int[aRows][bCols];
    for (int i = 0; i < aRows; i++) {
        for (int j = 0; j < bCols; j++) {
            for (int k = 0; k < aCols; k++) {
                result[i][j] += a[i][k] * b[k][j];
            }
        }
    }
    return result;
}

int[][] p = {{1, 2}, {3, 4}, {5, 6}};  // 3×2
int[][] q = {{7, 8, 9}, {10, 11, 12}}; // 2×3
int[][] product = matrixMultiply(p, q); // 3×3

for (int[] row : product) {
    System.out.println(Arrays.toString(row));
}
// [27, 30, 33]
// [61, 68, 75]
// [95, 106, 117]

// ── Checking if a matrix is symmetric: ───────────────────────────────
public static boolean isSymmetric(int[][] matrix) {
    int n = matrix.length;
    for (int i = 0; i < n; i++) {
        if (matrix[i].length != n) return false;  // must be square
        for (int j = 0; j < i; j++) {             // only lower triangle
            if (matrix[i][j] != matrix[j][i]) return false;
        }
    }
    return true;
}

int[][] sym = {{1, 2, 3}, {2, 5, 6}, {3, 6, 9}};
System.out.println(isSymmetric(sym));  // true

Related Topics in Arrays

Array Basics
An array in Java is a fixed-size, ordered collection of elements of the same type stored in a contiguous block of memory. Once created, the size of an array cannot change. Arrays are the simplest and most fundamental data structure in Java — they underlie many higher-level collections and provide direct, indexed access to elements in constant time O(1). Every array in Java is an object, inherits from java.lang.Object, and can hold either primitives or references.
One-Dimensional Array
A one-dimensional array is a linear sequence of elements of the same type, accessed by a single integer index. It is the simplest array form in Java and the foundation for understanding multidimensional arrays and collection classes. One-dimensional arrays are used to store and process lists of values — student grades, product prices, daily temperatures, character sequences — any time you need to work with a fixed-size ordered collection of homogeneous data.
Multidimensional Array
A multidimensional array in Java is an array with more than two dimensions — an array of arrays of arrays, and so on. While two-dimensional arrays suffice for most programming tasks, three-dimensional arrays are used for spatial data (x, y, z coordinates of a 3D grid), volumetric data (layers, rows, columns), RGB pixel buffers, and scientific computations involving tensors. Java supports arbitrary dimensionality, though arrays beyond three dimensions are rare in practice.
Jagged Array
A jagged array (also called a ragged array) is a multidimensional array where the inner arrays have different lengths. Unlike a rectangular 2D array where every row has the same number of columns, a jagged array allows each row to have its own independently sized inner array. This is possible because Java implements multidimensional arrays as arrays of arrays, making each inner array an independent object that can have any length.