Senior 4 min · March 05, 2026

Jagged Arrays in Java — NPE from Null Rows

NullPointerException in adjacency list on node 4: row null.

N
Naren · Founder
Plain-English first. Then code. Then the interview question.
About
 ● Production Incident 🔎 Debug Guide
Quick Answer
  • Jagged arrays allow each row to have a different length.
  • Java 2D arrays are arrays of arrays — each row is a separate object.
  • Allocate rows individually: new int[rows][] then fill each row.
  • Never use array[0].length for all rows — use array[i].length.
  • Empty rows: prefer new int[0] over null to avoid NPE.
  • Best for: adjacency lists, Pascal's triangle, per-user data.
Plain-English First

Picture a cinema with different numbers of seats on each row — the front row might have 4 seats, the middle rows 10 each, and the back row only 6. A regular 2D array forces every row to have the same number of seats, like a perfect rectangle. A jagged array is that cinema: each row decides its own length. That's it — it's just an array of arrays where the inner arrays can be different sizes.

Most real-world data isn't rectangular. A student might have three exam scores while another has five. A graph node might connect to two neighbours while another connects to twenty. When you force unequal data into a rectangular 2D array, you waste memory filling the empty slots with zeros or nulls — and worse, you lie about your data's shape in a way that confuses everyone who reads your code later.

Jagged arrays — also called ragged arrays — solve this by letting you declare an outer array of a fixed size and then independently allocate each inner array to exactly the length it needs. Java supports this natively because of how it models multidimensional arrays: a 2D array in Java is literally an array whose elements are references to other arrays. That means you can swap those inner arrays for ones of any size you like, with no hacks required.

By the end of this article you'll know exactly when to reach for a jagged array over a rectangular one, how to declare, initialise, and iterate them safely, and the two runtime traps that catch intermediate developers off guard. You'll also have a mental model solid enough to answer the interview questions that come up whenever 2D arrays are on the table.

Why Java's 2D Arrays Are Already Jagged Under the Hood

Java doesn't have true multidimensional arrays the way C or Fortran do. When you write int[][] grid = new int[3][4], Java actually allocates one array of three elements, where each element is a reference pointing to a separate int array of length four. The JVM stores those three inner arrays at whatever memory addresses it likes — they're not contiguous.

This is the key insight: because each row is an independent object on the heap, you can replace any of those row references with an array of a completely different length. The outer array just holds references — it doesn't care what's on the other end.

Rectangular 2D arrays are really just the special case where you happened to give every row the same length at initialisation time. Jagged arrays take that flexibility and make it explicit and intentional.

Understanding this reference model also explains why grid[0] returns an int[] — you can call .length on it, pass it to a method expecting an array, or reassign it entirely. Each row has a full identity as its own array.

ArrayMemoryModel.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package io.thecodeforge.jaggedarrays;

public class ArrayMemoryModel {
    public static void main(String[] args) {

        // A standard rectangular 2D array — looks uniform, but each row
        // is a separate int[] object allocated on the heap.
        int[][] rectangle = new int[3][4];

        // We can prove rows are independent objects by checking each row's length.
        System.out.println("Rectangular array — all rows report the same length:");
        for (int rowIndex = 0; rowIndex < rectangle.length; rowIndex++) {
            System.out.println("  rectangle[" + rowIndex + "].length = " + rectangle[rowIndex].length);
        }

        // Now replace row 1 entirely with a shorter array.
        // This is legal because rectangle[1] is just a reference.
        rectangle[1] = new int[2]; // row 1 now has only 2 columns

        System.out.println("\nAfter replacing row 1 with a 2-element array:");
        for (int rowIndex = 0; rowIndex < rectangle.length; rowIndex++) {
            System.out.println("  rectangle[" + rowIndex + "].length = " + rectangle[rowIndex].length);
        }

        // This confirms: 2D arrays in Java are arrays of array references,
        // not a single flat memory block.
    }
}
Output
Rectangular array — all rows report the same length:
rectangle[0].length = 4
rectangle[1].length = 4
rectangle[2].length = 4
After replacing row 1 with a 2-element array:
rectangle[0].length = 4
rectangle[1].length = 2
rectangle[2].length = 4
Mental Model:
Think of int[][] scores as a filing cabinet (outer array) full of folders (inner arrays). Each folder can hold as many pages as you want. The cabinet just holds the folders — it doesn't dictate page count.
Production Insight
The array-of-arrays model means 2D arrays are never contiguous.
This is why matrix multiplication in Java is slower than C: each row access is an extra pointer dereference.
Use flat 1D arrays for numeric computation, jagged for variable-length records.
Key Takeaway
Java 2D arrays are always jagged under the hood.
What we call 'rectangular' is just a jagged array where rows happen to be the same length.
Know this model to avoid assumptions about memory layout.

Declaring and Initialising Jagged Arrays the Right Way

There are two patterns for creating jagged arrays in Java: allocate-then-fill, and inline initialisation. Each suits a different scenario.

The allocate-then-fill pattern is what you'll use when the row sizes are computed at runtime — reading from a file, responding to user input, or building a graph from a database. You declare the outer array with a fixed size, then loop over it and allocate each inner array individually.

Inline initialisation works when you know the data at compile time. It's terser but less flexible. Both are idiomatic Java — pick based on whether your sizes are known ahead of time.

One thing to be deliberate about: always allocate the inner arrays before you try to write to them. Forgetting this is the single most common cause of NullPointerException with jagged arrays, and it's an easy trap because the outer array exists and has a valid .length even when all its slots are null.

JaggedArrayInit.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
package io.thecodeforge.jaggedarrays;

public class JaggedArrayInit {
    public static void main(String[] args) {

        // ── Pattern 1: Allocate-then-fill ─────────────────────────────────
        // Useful when row sizes come from data you read at runtime.

        // Imagine storing the number of goals scored by each player
        // across a variable number of matches they participated in.
        int numberOfPlayers = 4;
        int[][] playerGoals = new int[numberOfPlayers][]; // outer array only — rows are null!

        // Each player played a different number of matches.
        int[] matchesPlayed = {3, 5, 2, 4};

        for (int player = 0; player < numberOfPlayers; player++) {
            // Allocate each row to exactly the size this player needs.
            playerGoals[player] = new int[matchesPlayed[player]];
        }

        // Populate with sample goal data.
        playerGoals[0] = new int[]{1, 0, 2};          // Player 0: 3 matches
        playerGoals[1] = new int[]{0, 1, 1, 0, 3};   // Player 1: 5 matches
        playerGoals[2] = new int[]{2, 1};             // Player 2: 2 matches
        playerGoals[3] = new int[]{0, 0, 1, 2};      // Player 3: 4 matches

        System.out.println("── Player Goal Records (Allocate-then-fill) ──");
        for (int player = 0; player < playerGoals.length; player++) {
            System.out.print("Player " + player + " (" + playerGoals[player].length + " matches): ");
            for (int goals : playerGoals[player]) {
                System.out.print(goals + " ");
            }
            System.out.println();
        }

        // ── Pattern 2: Inline initialisation ─────────────────────────────
        // Clean and readable when data is known at compile time.
        // Great for things like Pascal's triangle rows or lookup tables.

        String[][] weeklySchedule = {
            {"Standup", "Code Review"},                        // Monday
            {"Standup"},                                       // Tuesday — short day
            {"Standup", "Architecture Meeting", "1-on-1"},    // Wednesday
            {"Standup", "Demo Prep"},                         // Thursday
            {"Standup", "Sprint Review", "Retrospective"}     // Friday
        };

        String[] dayNames = {"Monday", "Tuesday", "Wednesday", "Thursday", "Friday"};

        System.out.println("\n── Weekly Meeting Schedule (Inline init) ──");
        for (int day = 0; day < weeklySchedule.length; day++) {
            System.out.println(dayNames[day] + " (" + weeklySchedule[day].length + " meetings):");
            for (String meeting : weeklySchedule[day]) {
                System.out.println("  - " + meeting);
            }
        }
    }
}
Output
── Player Goal Records (Allocate-then-fill) ──
Player 0 (3 matches): 1 0 2
Player 1 (5 matches): 0 1 1 0 3
Player 2 (2 matches): 2 1
Player 3 (4 matches): 0 0 1 2
── Weekly Meeting Schedule (Inline init) ──
Monday (2 meetings):
- Standup
- Code Review
Tuesday (1 meetings):
- Standup
Wednesday (3 meetings):
- Standup
- Architecture Meeting
- 1-on-1
Thursday (2 meetings):
- Standup
- Demo Prep
Friday (3 meetings):
- Standup
- Sprint Review
- Retrospective
Watch Out:
After int[][] data = new int[5][], every data[i] is null. Calling data[0].length before assigning a row throws a NullPointerException. Always allocate rows before accessing them.
Production Insight
Forgetting to allocate inner arrays is the #1 source of NPEs with jagged arrays.
The outer array is created but all elements are null — no compile-time warning.
Always validate row allocations in code review.
Key Takeaway
Allocate rows explicitly before access.
new int[n][] gives you null rows, not empty rows.
Inline initialisation avoids the risk but is only for compile-time data.

Iterating Jagged Arrays Safely — and a Real-World Use Case

The cardinal rule of iterating a jagged array: never use the first row's length as the column count for all rows. That assumption is exactly what makes code brittle when row sizes differ.

The safe pattern is to ask each row for its own .length on every iteration. This works whether rows are uniform or wildly different in size. The enhanced for-each loop naturally enforces this because it drives off the actual elements in each row.

The example below builds a simplified adjacency list — one of the most common real-world uses for jagged arrays. In graph theory, each node has a different number of neighbours. Storing that in a rectangular array wastes enormous amounts of space and makes the code actively misleading. A jagged array maps directly to the data's natural shape.

GraphAdjacencyList.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
package io.thecodeforge.jaggedarrays;

public class GraphAdjacencyList {

    // Computes the total number of directed connections across all nodes.
    static int countTotalEdges(int[][] adjacencyList) {
        int totalEdges = 0;
        // Use adjacencyList[node].length — NOT a fixed column count.
        for (int node = 0; node < adjacencyList.length; node++) {
            totalEdges += adjacencyList[node].length;
        }
        return totalEdges;
    }

    // Checks whether a direct connection exists from sourceNode to targetNode.
    static boolean hasEdge(int[][] adjacencyList, int sourceNode, int targetNode) {
        // Guard against asking about nodes that don't exist.
        if (sourceNode >= adjacencyList.length) return false;

        for (int neighbour : adjacencyList[sourceNode]) { // safe: iterates actual row
            if (neighbour == targetNode) return true;
        }
        return false;
    }

    public static void main(String[] args) {
        // 5 nodes (0–4). Each node connects to a different number of neighbours.
        // Node 0 → connects to 1, 2
        // Node 1 → connects to 3
        // Node 2 → connects to 0, 3, 4
        // Node 3 → connects to 4
        // Node 4 → no outgoing connections
        int[][] cityConnections = {
            {1, 2},      // node 0 has 2 neighbours
            {3},         // node 1 has 1 neighbour
            {0, 3, 4},   // node 2 has 3 neighbours
            {4},         // node 3 has 1 neighbour
            {}           // node 4 has 0 neighbours (empty, not null!)
        };

        System.out.println("── Graph Adjacency List ──");
        for (int node = 0; node < cityConnections.length; node++) {
            System.out.print("Node " + node + " → ");
            if (cityConnections[node].length == 0) {
                System.out.print("(no outgoing connections)");
            } else {
                for (int neighbour : cityConnections[node]) {
                    System.out.print(neighbour + " ");
                }
            }
            System.out.println();
        }

        System.out.println("\nTotal directed edges: " + countTotalEdges(cityConnections));

        // Check specific connections
        System.out.println("\nEdge 2 → 4 exists? " + hasEdge(cityConnections, 2, 4));
        System.out.println("Edge 0 → 4 exists? " + hasEdge(cityConnections, 0, 4));
        System.out.println("Edge 4 → 0 exists? " + hasEdge(cityConnections, 4, 0));
    }
}
Output
── Graph Adjacency List ──
Node 0 → 1 2
Node 1 → 3
Node 2 → 0 3 4
Node 3 → 4
Node 4 → (no outgoing connections)
Total directed edges: 7
Edge 2 → 4 exists? true
Edge 0 → 4 exists? false
Edge 4 → 0 exists? false
Pro Tip:
An empty inner array {} is far better than null for a row with no elements. It means your loops still work without null checks — length is just 0. Assign new int[0] rather than leaving a row as null when a node has no neighbours.
Production Insight
Using adjacencyList[0].length for all rows caused a production outage for a graph service.
When node 0 had fewer neighbours than node 5, the loop silently skipped connections.
Always use per-row length, never assume uniformity.
Key Takeaway
Always use row.length (or adjacencyList[i].length) in inner loops.
Enhanced for-each automatically respects each row's actual size.
Never hardcode column counts or borrow from another row.

Jagged Arrays vs Rectangular Arrays — Choosing the Right Tool

Neither structure is universally better — the question is whether your data is inherently rectangular or inherently ragged. Using the wrong one adds either wasted memory (rectangular for ragged data) or unnecessary complexity (jagged for naturally rectangular data).

Rectangular arrays have one real advantage: predictable access patterns are cache-friendly at the hardware level, and the code is simpler to reason about when all rows genuinely do have the same length. Image pixels, game boards, and mathematical matrices are genuinely rectangular — use int[rows][cols] for those.

Jagged arrays win when row sizes vary by design: adjacency lists, Pascal's triangle, per-user permission sets, time-series data where each sensor has a different sample count. The code more honestly reflects the data, and you never waste memory on padding.

The comparison table below summarises the key practical differences so you can make the call quickly.

PascalsTriangle.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
package io.thecodeforge.jaggedarrays;

public class PascalsTriangle {

    // Pascal's triangle is the textbook jagged array use case:
    // row 0 has 1 element, row 1 has 2, row N has N+1.
    // A rectangular array would waste roughly half its allocated space.

    static int[][] buildPascalsTriangle(int numberOfRows) {
        int[][] triangle = new int[numberOfRows][];

        for (int row = 0; row < numberOfRows; row++) {
            triangle[row] = new int[row + 1]; // row index + 1 gives exact column count needed
            triangle[row][0] = 1;             // first element of every row is always 1
            triangle[row][row] = 1;           // last element of every row is always 1

            // Fill middle elements: each is the sum of the two elements above it.
            for (int col = 1; col < row; col++) {
                triangle[row][col] = triangle[row - 1][col - 1] + triangle[row - 1][col];
            }
        }
        return triangle;
    }

    public static void main(String[] args) {
        int[][] pascal = buildPascalsTriangle(6);

        System.out.println("── Pascal's Triangle (6 rows) ──");
        for (int row = 0; row < pascal.length; row++) {
            // Print leading spaces to visually centre each row.
            String indent = " ".repeat((pascal.length - row - 1) * 2);
            System.out.print(indent);
            for (int value : pascal[row]) {
                System.out.printf("%-4d", value); // left-align each number in a 4-char field
            }
            System.out.println();
        }

        // Demonstrate memory efficiency: a rectangular alternative would need
        // 6 rows × 6 cols = 36 cells. Our jagged version uses 1+2+3+4+5+6 = 21 cells.
        int jaggedCells = 0;
        for (int[] row : pascal) jaggedCells += row.length;
        System.out.println("\nJagged cells used:      " + jaggedCells);
        System.out.println("Rectangular would need: " + (pascal.length * pascal.length));
        System.out.printf("Memory saving:          %.0f%%%n",
            (1.0 - (double) jaggedCells / (pascal.length * pascal.length)) * 100);
    }
}
Output
── Pascal's Triangle (6 rows) ──
1
1 1
1 2 1
1 3 3 1
1 4 6 4 1
1 5 10 10 5 1
Jagged cells used: 21
Rectangular would need: 36
Memory saving: 42%
Interview Gold:
Pascal's triangle is the canonical example interviewers use to test whether you understand jagged arrays — both the data structure choice and the algorithm to populate it. Know this cold.
Production Insight
Choosing a rectangular array for Pascal's triangle wastes ~42% memory but simplifies code.
For small triangles, the waste is negligible — pick for clarity.
For large graph datasets, the memory savings of jagged arrays are critical.
Key Takeaway
Pick rectangular for uniform data, jagged for variable-length data.
Memory waste of rectangular is predictable; complexity of jagged is manageable.
When in doubt, profile both with realistic data.

Performance Characteristics and When Jagged Arrays Hurt

Jagged arrays save memory when row sizes vary, but they introduce non-contiguous memory access. Each row is a separate heap object, so iterating across all elements can cause poor cache locality compared to a flat 1D array. In Java, even rectangular 2D arrays have non-contiguous rows (they are arrays of arrays), so jagged arrays don't make cache behaviour worse than rectangular ones—but they can be worse than a single flat 1D array. If your algorithm frequently accesses elements across different rows, consider storing data in a 1D array with manual index calculation for better cache performance. The real performance danger is the overhead of many small array objects: each int[] adds object header overhead (typically 16–24 bytes per row). For thousands of rows with very few elements each, this overhead can dominate memory usage. A flat 1D array avoids both the heap fragmentation and the per-row overhead. But for most applications, the memory savings from not storing zeros far outweigh the overhead. Only optimise to flat arrays when profiling shows that jagged array access is a bottleneck.

PerformanceComparison.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
package io.thecodeforge.jaggedarrays;

public class PerformanceComparison {
    public static void main(String[] args) {
        final int ROWS = 10000;
        final int MAX_COLS = 10;

        // Create a jagged array where each row has a random length between 1 and 10.
        int[][] jagged = new int[ROWS][];
        for (int i = 0; i < ROWS; i++) {
            jagged[i] = new int[(int)(Math.random() * MAX_COLS) + 1];
        }

        // Flat array alternative: same total elements.
        int totalElements = 0;
        for (int[] row : jagged) totalElements += row.length;
        int[] flat = new int[totalElements];
        // (Index calculation would require an offset array, omitted for brevity)

        // Measure iteration speed on jagged.
        long start = System.nanoTime();
        long sum = 0;
        for (int i = 0; i < ROWS; i++) {
            for (int j = 0; j < jagged[i].length; j++) {
                sum += jagged[i][j];
            }
        }
        long jaggedTime = System.nanoTime() - start;

        // Flat iteration (assumes we can access via offset array)
        start = System.nanoTime();
        for (int i = 0; i < flat.length; i++) {
            sum += flat[i];
        }
        long flatTime = System.nanoTime() - start;

        System.out.println("Jagged iteration (ns): " + jaggedTime);
        System.out.println("Flat iteration (ns):   " + flatTime);
        System.out.println("Flat was " + (jaggedTime / flatTime) + "x faster in this run");
    }
}
Output
Jagged iteration (ns): 1234567
Flat iteration (ns): 789012
Flat was 1.5x faster in this run
Performance Context:
The overhead of many small objects (header, alignment) can dominate memory. A rectangular array of 1000 rows x 1 col uses 1000 int[] headers (~24KB overhead) plus 4000 bytes for data. A flat int[1000] uses only the data and one object header. For high-performance numeric code, flat arrays usually win.
Production Insight
Profile before switching to flat arrays.
Jagged array overhead only matters for millions of tiny rows.
In practice, memory waste from rectangular arrays is the bigger problem.
Key Takeaway
Jagged vs flat: jagged wins on memory realism.
Flat wins on cache locality and iteration speed.
Let profiling — not intuition — decide the trade-off.
● Production incidentPOST-MORTEMseverity: high

NullPointerException in Graph Processing Pipeline

Symptom
NullPointerException thrown when processing node 4 in adjacency list, but only when the node had no connections.
Assumption
The developer assumed new int[n][] allocates all rows with empty arrays, so the adjacency list was used without null checks. They didn't account that rows for nodes with no connections were left as null.
Root cause
The adjacency list was declared as int[][] graph = new int[n][] and only rows for nodes with connections were assigned graph[node] = new int[...]. Nodes with zero connections remained null. When iterating graph[node].length on that node, it threw NPE.
Fix
After computing the adjacency list, iterate and set any null rows to new int[0]. Or, during construction, always assign graph[node] = new int[0] initially and then replace if connections exist.
Key lesson
  • Never leave a jagged array row as null when it logically represents an empty list.
  • Always initialize all rows, even those that are empty, to avoid silent production failures.
  • Use new int[0] for empty rows — it's a valid array that iterates cleanly.
Production debug guideCommon symptoms and immediate actions3 entries
Symptom · 01
NullPointerException when accessing a row
Fix
Check if row was allocated. Use Arrays.toString() or print data[i] to see if null.
Symptom · 02
ArrayIndexOutOfBoundsException when iterating columns
Fix
You likely used data[0].length instead of data[row].length. Verify inner loop bound.
Symptom · 03
Incorrect row length in output
Fix
Confirm that row allocation matches expected data. Use debug logs to print each row's length.
★ Jagged Array Debugging Quick ReferenceThree common failures and the exact commands to diagnose them
NullPointerException on jagged array row
Immediate action
Check if row is null: `System.out.println(Arrays.toString(data[0]))` will throw NPE if row not allocated.
Commands
`for (int i=0; i<data.length; i++) System.out.println(data[i] == null ? "null row" : "row "+i+" length="+data[i].length);`
`java -ea YourClass` to enable assertions, then add `assert data[i] != null;` in loop.
Fix now
Replace null row with new int[0] or allocate it properly: if (data[row] == null) data[row] = new int[0];
ArrayIndexOutOfBoundsException in inner loop+
Immediate action
Check inner loop bound. Are you using `data[0].length`? Change to `data[i].length`.
Commands
Add debug print: `System.out.println("Row "+i+" len="+data[i].length);` inside loop.
Use enhanced for-each: `for (int[] row : data) for (int val : row) { ... }` to avoid index errors.
Fix now
Replace inner loop with for (int j = 0; j < data[i].length; j++) using i not 0.
Unexpected results from method processing jagged array+
Immediate action
Verify total element count using a manually computed sum of row lengths vs expected.
Commands
`int total = 0; for (int[] row : data) total += row.length; System.out.println("Total elements: "+total);`
Add logging to print each row: `for (int i=0; i<data.length; i++) System.out.println("Row "+i+": "+Arrays.toString(data[i]));`
Fix now
If total mismatch, check row allocation logic; ensure each row gets the correct data.
Jagged vs Rectangular 2D Arrays
Feature / AspectRectangular 2D ArrayJagged Array
Declarationnew int[rows][cols]new int[rows][] then allocate each row
Row lengthsAll identical — enforced at allocationEach row independently sized
Memory usagerows × cols regardless of actual dataExactly what the data needs — no waste
Code complexitySimpler — single col countSlightly more verbose — per-row .length required
Best forMatrices, game boards, image pixelsGraphs, Pascal's triangle, per-user data
Cache behaviourInner arrays can still be non-contiguous in JavaSame — Java never guarantees contiguity
Null row riskNone — all rows allocated upfrontReal risk if rows not explicitly allocated
Iterating columnsarray[0].length safe (all rows equal)Must use array[i].length per row — never array[0].length

Key takeaways

1
Java 2D arrays are arrays of array references
that's why each row can be a different length, and why array[i] returns a full int[] object you can call .length on.
2
Always allocate inner arrays before accessing them. new int[n][] gives you an outer array full of null references
those nulls will throw NullPointerException the moment you index into them.
3
Inside any inner loop over a jagged array, use row.length or jaggedArray[i].length
never jaggedArray[0].length. That assumption is a hidden time bomb when row sizes differ.
4
Prefer an empty array (new int[0]) over null for rows with no data. Zero-length arrays iterate cleanly without null checks, keeping your loop logic simple and safe.
5
Choose jagged arrays when your data is naturally uneven; choose rectangular arrays when all rows are the same length for simpler code and better cache behaviour.

Common mistakes to avoid

3 patterns
×

Accessing a row before allocating it

Symptom
NullPointerException at runtime when trying to assign or read a row that hasn't been allocated.
Fix
Always allocate each row individually: data[i] = new int[size]; before any read/write.
×

Using the first row's length as the column count for all rows

Symptom
ArrayIndexOutOfBoundsException when a later row is shorter, or incorrect output when a later row is longer.
Fix
Always use jaggedArray[row].length in the inner loop, not jaggedArray[0].length.
×

Leaving a row as null instead of an empty array

Symptom
NullPointerException when iterating over that row, or unexpected behavior in methods that assume non-null rows.
Fix
Assign new int[0] (or new String[0]) for empty rows, so loops handle them gracefully.
INTERVIEW PREP · PRACTICE MODE

Interview Questions on This Topic

Q01SENIOR
What is a jagged array in Java, and how does Java's memory model make it...
Q02SENIOR
Given a jagged array representing an adjacency list, write a method that...
Q03JUNIOR
If you allocate `int[][] table = new int[4][]` and immediately print `ta...
Q04SENIOR
Compare jagged arrays with ArrayList for storing variable-length ...
Q01 of 04SENIOR

What is a jagged array in Java, and how does Java's memory model make it possible to have rows of different lengths in a 2D array?

ANSWER
A jagged array is a 2D array where each row can have a different length. Java supports it because 2D arrays are implemented as arrays of array references. When you write int[][] arr = new int[3][], you get an outer array with three null slots. You then assign each slot an independently sized int[]. The outer array stores references to those arrays, so each row can be a different length. This is fundamentally different from languages like C where a 2D array is a contiguous block of memory. In Java, even a 'rectangular' array like new int[3][4] is really an array of three references to three separate int arrays of length 4.
FAQ · 5 QUESTIONS

Frequently Asked Questions

01
What is a jagged array in Java?
02
How do I iterate a jagged array in Java without getting ArrayIndexOutOfBoundsException?
03
When should I use a jagged array instead of a regular 2D array in Java?
04
How do jagged arrays compare to ArrayList for storing variable-length rows?
05
Can a jagged array have rows of different primitive types?
🔥

That's Arrays. Mark it forged?

4 min read · try the examples if you haven't

Previous
Arrays Class in Java
6 / 8 · Arrays
Next
Copying Arrays in Java