☕ Java

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.

What Jagged Arrays Are and When to Use Them

In most languages, a 2D array is a rectangle — every row has the same number of columns. Java's implementation of 2D arrays as arrays of independent inner arrays means this rectangular constraint does not exist. Each row can have a different length, producing what is called a jagged or ragged array — like a staircase when visualised. Jagged arrays model data that is inherently non-rectangular. A triangle of numbers where row 0 has 1 element, row 1 has 2 elements, row 2 has 3 elements, and so on — this is precisely a jagged array. Pascal's triangle, a lower triangular matrix, an adjacency list for a graph, a list of sentences where each sentence has a different number of words, and a collection of students where each student has taken a different number of courses — all of these are naturally jagged. Compared to padding a rectangular array with dummy values to fill unused positions, jagged arrays save memory for sparse or triangular data. A lower triangular matrix of size n×n needs n*(n+1)/2 elements, not n². For large n, this is approximately half the memory. The trade-off is slightly more complex traversal code — you cannot assume all rows have the same length and must use row.length for the inner loop bound. Jagged arrays are also used when the inner array sizes are computed dynamically — when the number of elements in each row is determined by input data or runtime conditions rather than fixed at design time. Each inner array is allocated separately, so rows can be sized exactly to their needs without wasting space.
Java
// ── Jagged array — rows with different lengths: ───────────────────────
int[][] jagged = new int[4][];   // outer array: 4 rows, inner arrays: null
// jagged[0] = null, jagged[1] = null, ... (inner arrays not yet created)

jagged[0] = new int[1];    // row 0: 1 element
jagged[1] = new int[3];    // row 1: 3 elements
jagged[2] = new int[2];    // row 2: 2 elements
jagged[3] = new int[4];    // row 3: 4 elements

// ── Memory model: ─────────────────────────────────────────────────────
// jagged ──────────▶ [ref0] [ref1] [ref2] [ref3]
//                      │      │      │      │
//                      ▼      ▼      ▼      ▼
//                    [?]   [?][?][?]  [?][?]  [?][?][?][?]
//                    1 element 3 elements 2 elements 4 elements

// ── Initialiser syntax for jagged array: ──────────────────────────────
int[][] triangle = {
    {1},
    {2, 3},
    {4, 5, 6},
    {7, 8, 9, 10}
};

// Row lengths are different — must use row.length for each row:
for (int row = 0; row < triangle.length; row++) {
    System.out.printf("Row %d (%d elements): %s%n",
        row,
        triangle[row].length,
        Arrays.toString(triangle[row]));
}
// Row 0 (1 elements): [1]
// Row 1 (2 elements): [2, 3]
// Row 2 (3 elements): [4, 5, 6]
// Row 3 (4 elements): [7, 8, 9, 10]

// ── total elements: ──────────────────────────────────────────────────
int total = 0;
for (int[] row : triangle) total += row.length;
System.out.println("Total elements: " + total);  // 10

Traversing Jagged Arrays Correctly

The critical difference in traversing jagged arrays versus rectangular arrays is that you cannot assume every row has the same length. Using a fixed column count (like the length of the first row) will crash with ArrayIndexOutOfBoundsException for any shorter row or silently miss elements of longer rows. Always use array[row].length for the inner loop bound — this is both correct and efficient since length is a direct field access. The null row problem is another pitfall specific to jagged arrays created with the two-step method (declaring the outer array then assigning inner arrays). If any inner array is not assigned, it remains null. Iterating over rows without null checking will throw NullPointerException when the inner loop tries to access the null row's length. Always initialise all inner arrays before traversal. When the shape of the jagged array is data-driven rather than predetermined, a null check or a check of row != null is the safe traversal pattern. For algorithms that need to know whether a row exists (as opposed to existing but being empty), distinguishing null (not initialised) from new int[0] (initialised but empty) is important.
Java
// ── Always use row.length — never assume fixed column count: ─────────
String[][] sentences = {
    {"The", "cat", "sat"},
    {"Dogs", "bark"},
    {"Java", "is", "an", "object-oriented", "language"},
    {"Hello"}
};

// WRONG — assumes all rows have 3 columns like row 0:
// for (int r = 0; r < sentences.length; r++) {
//     for (int c = 0; c < sentences[0].length; c++) {  // BUG!
//         System.out.print(sentences[r][c] + " ");
//     }
// }

// CORRECT — use sentences[r].length for each row:
for (int r = 0; r < sentences.length; r++) {
    for (int c = 0; c < sentences[r].length; c++) {
        System.out.print(sentences[r][c] + " ");
    }
    System.out.println();
}
// The cat sat
// Dogs bark
// Java is an object-oriented language
// Hello

// ── For-each traversal: ───────────────────────────────────────────────
for (String[] row : sentences) {
    for (String word : row) {
        System.out.print(word + " ");
    }
    System.out.println();
}

// ── Null inner arrays — check before traversal: ───────────────────────
int[][] sparse = new int[5][];   // outer array, inner arrays null
sparse[0] = new int[]{10, 20};
// sparse[1] remains null
sparse[2] = new int[]{30};
// sparse[3] and sparse[4] remain null

for (int r = 0; r < sparse.length; r++) {
    if (sparse[r] == null) {
        System.out.println("Row " + r + ": null (not initialised)");
    } else {
        System.out.println("Row " + r + ": " + Arrays.toString(sparse[r]));
    }
}
// Row 0: [10, 20]
// Row 1: null (not initialised)
// Row 2: [30]
// Row 3: null (not initialised)
// Row 4: null (not initialised)

Practical Uses of Jagged Arrays

Jagged arrays appear in several recurring patterns in Java programming. Pascal's triangle is the classic academic example — each row has one more element than the previous, and each element is the sum of the two elements above it in the triangle. Building it with a jagged array is more memory-efficient and semantically accurate than using a rectangular array padded with zeros. Adjacency list representation of graphs is one of the most practically important uses of jagged arrays. A graph with n vertices is represented as int[n][], where row i contains the indices of all neighbours of vertex i. Vertices with many neighbours have long rows; vertices with few neighbours have short rows. This is far more space-efficient than an n×n adjacency matrix for sparse graphs where most vertices have few connections. Storing variable-length data per record — such as the list of courses for each student, the items in each order, or the tags for each article — is another natural application. When data items have a one-to-many relationship and the "many" side varies, a jagged array (or more commonly a List<int[]> or List<List<T>>) represents the structure naturally without wasted space.
Java
// ── Pascal's Triangle: ───────────────────────────────────────────────
public static int[][] buildPascalsTriangle(int numRows) {
    int[][] triangle = new int[numRows][];

    for (int row = 0; row < numRows; row++) {
        triangle[row] = new int[row + 1];  // row i has i+1 elements
        triangle[row][0] = 1;              // first element always 1
        triangle[row][row] = 1;            // last element always 1

        // Interior elements: sum of two above
        for (int col = 1; col < row; col++) {
            triangle[row][col] =
                triangle[row-1][col-1] + triangle[row-1][col];
        }
    }
    return triangle;
}

int[][] pascal = buildPascalsTriangle(6);
for (int[] row : pascal) {
    System.out.println(Arrays.toString(row));
}
// [1]
// [1, 1]
// [1, 2, 1]
// [1, 3, 3, 1]
// [1, 4, 6, 4, 1]
// [1, 5, 10, 10, 5, 1]

// ── Graph adjacency list: ─────────────────────────────────────────────
// Undirected graph: 0-1, 0-2, 1-3, 2-3, 3-4
// Vertex 0: connects to 1 and 2
// Vertex 1: connects to 0 and 3
// Vertex 2: connects to 0 and 3
// Vertex 3: connects to 1, 2, and 4
// Vertex 4: connects to 3

int[][] adjacency = {
    {1, 2},       // vertex 0's neighbours
    {0, 3},       // vertex 1's neighbours
    {0, 3},       // vertex 2's neighbours
    {1, 2, 4},    // vertex 3's neighbours — more connections
    {3}           // vertex 4's neighbours — fewer connections
};

// BFS (Breadth-First Search) using adjacency list:
public static void bfs(int[][] adj, int start) {
    boolean[] visited = new boolean[adj.length];
    Queue<Integer> queue = new LinkedList<>();

    visited[start] = true;
    queue.offer(start);

    while (!queue.isEmpty()) {
        int vertex = queue.poll();
        System.out.print(vertex + " ");

        for (int neighbour : adj[vertex]) {  // jagged — varies per vertex
            if (!visited[neighbour]) {
                visited[neighbour] = true;
                queue.offer(neighbour);
            }
        }
    }
    System.out.println();
}

bfs(adjacency, 0);   // 0 1 2 3 4

// ── Variable-length records — courses per student: ────────────────────
String[][] studentCourses = {
    {"Maths", "Physics", "Chemistry"},           // student 0: 3 courses
    {"English", "History"},                       // student 1: 2 courses
    {"Computer Science", "Maths", "Further Maths", "Physics"},  // student 2: 4 courses
    {"Art"}                                       // student 3: 1 course
};

for (int s = 0; s < studentCourses.length; s++) {
    System.out.printf("Student %d (%d courses): %s%n",
        s, studentCourses[s].length,
        String.join(", ", studentCourses[s]));
}

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