Jagged Arrays
Vaibhav • September 12, 2025
Arrays are fundamental, but real-world data isn’t always a neat rectangle. When each “row” needs a different length - for example, students with varying numbers of test scores or levels with different enemy counts - a jagged array (an array of arrays) gives you flexible, memory-friendly storage. In this article we’ll explain what jagged arrays are, how to create and use them, why their memory layout matters, and when to prefer them over rectangular (multidimensional) arrays. All examples are short, well-indented, and explained line-by-line.
What is a jagged array?
A jagged array is simply an array whose elements are themselves arrays. In C# this is written with
double-square-brackets, for example int[][]
. Each row can have a different
length, making jagged arrays ideal for non-uniform data.
int[][] jagged = new int[3][]; // parent array with 3 rows; each row is currently null
Key points:
- The parent array holds references to child arrays (not the child data inline).
- Each child (row) must be initialized separately.
- Rows can differ in length - that’s the main benefit.
Initializing jagged arrays
You can initialize rows one by one or inline. Below are both styles - both are common and readable.
// Step-by-step initialization int[][] jagged = new int[3][]; // create parent array with 3 slots jagged[0] = new int[2]; // row 0 has 2 elements jagged[1] = new int[4]; // row 1 has 4 elements jagged[2] = new int[3]; // row 2 has 3 elements
// Inline initializer (equivalent)
int[][] predefined = new int[][]
{
new int[] { 1, 2 },
new int[] { 3, 4, 5, 6 },
new int[] { 7, 8, 9 }
};
Line-by-line: the parent array (jagged
) has three slots. Each
slot is assigned a fresh array with its own Length
. With the inline
initializer, you both allocate the child arrays and populate them with values in one expression.
Accessing and iterating
Access uses two indexes: jagged[row][col]
. When iterating, always query the
current row’s length rather than assuming a uniform size.
// Safe iteration of a jagged array for (int r = 0; r < predefined.Length; r++) { for (int c = 0; c < predefined[r].Length; c++) { Console.Write(predefined[r][c] + " "); } Console.WriteLine(); }
This prints:
1 2 3 4 5 6 7 8 9
When to use jagged arrays
Good scenarios include:
- Irregular row lengths - student test scores where each student took a different number of exams.
- Memory savings - avoid allocating unused cells in a rectangular array when rows vary widely in size.
- Representing lists-of-lists where each sub-list has independent lifetime or semantics.
Prefer jagged arrays when rows genuinely differ in length. If every row is the same length, a rectangular
multidimensional array (int[,]
) is usually clearer and more cache-friendly.
Memory layout and performance implications
Jagged arrays are implemented as a parent array of references to independently allocated child arrays. That means:
- Each child array lives at its own location on the managed heap.
- Accessing
jagged[r][c]
does an extra pointer dereference compared to a rectangular array. - Because rows are separate, rows can be collected or replaced independently.
The extra indirection makes jagged arrays slightly less cache-friendly than
contiguous rectangular arrays. For very hot inner loops over truly rectangular data, int[,]
can be faster.
Practical example - student grades
A small real-world-style example (no classes or complex types - just arrays and loops) shows why jagged arrays are convenient.
// Store student grades where each student has taken a different number of tests int[][] grades = new int[][] { new int[] { 92, 85 }, // student 0 new int[] { 78, 88, 91, 95 }, // student 1 new int[] { 100 } // student 2 };
// Compute and print simple averages
for (int s = 0; s < grades.Length; s++)
{
int sum = 0;
for (int t = 0; t < grades[s].Length; t++)
{
sum += grades[s][t];
}
double avg = (double)sum / grades[s].Length;
Console.WriteLine($"Student {s} average: {avg:F1}");
}
Jagged arrays vs multidimensional arrays
Quick comparison to help you choose:
- Jagged - flexible lengths, parent holds references, slight extra indirection, easier when rows vary.
- Rectangular (multidimensional) - single contiguous block, better locality, simpler when shape is fixed.
The runtime stores jagged-array child arrays independently, so you can replace an entire row easily by
assigning a new array to jagged[row]
. That’s handy for dynamic datasets.
Common pitfalls
- Forgetting to initialize a row: accessing
jagged[0][0]
before settingjagged[0]
causes aNullReferenceException
. - Assuming uniform length: always check
jagged[row].Length
. - Resizing rows: arrays are fixed-size once created - to change a row’s length you must allocate a new array and copy data if needed.
Summary
Jagged arrays (arrays of arrays) are the right tool when rows differ in size or when you want independent row lifetime. They’re simple to initialize, access, and iterate - just remember to initialize each row and use the row length when iterating. When rows are uniform and performance-critical, consider rectangular arrays; for variable row lengths, jagged arrays keep memory usage lean and intent clear.
Next up: in the following article we’ll explore array methods and properties - handy
built-in helpers like Length
, Array.Copy
,
and Array.Sort
that are useful with both jagged and rectangular arrays.