← Open in the full interactive course (progress tracking, search & more)

A jagged array is an array of arrays — each row is its own independent array, free to be a completely different length.

In the last lesson, a rectangular array modeled a theater with 8 seats in every single row — a perfect grid. But real data is often messier than that. A company's engineering team might have 6 people, its sales team 14, and its support team 3. There's no rectangle that fits that shape without wasting space or lying about how many "seats" each team has.

In this lesson, you'll learn what a jagged array is, how it differs from the rectangular array you just learned, when its flexibility is exactly what you need, and the trade-offs it brings.

What Is It?

The Simple Explanation

A jagged array is an array where each element is itself a separate array — and those inner arrays don't have to be the same length. Picture a bookshelf where each shelf holds a different number of books. The shelf itself is one row; how many books sit on it is entirely up to that shelf.

The Technical Definition

In C#, a jagged array is written with separate pairs of square brackets: T[][]. The outer array is a normal 1D array whose element type happens to be another array (T[]). Each of those inner arrays is independently created, independently sized, and can even be null until you assign it.

Jagged Array = An Array Whose Elements Are Arrays

Why Does It Exist?

The Problem

Suppose you want to store the team members in every department of a company: Engineering has 6 people, Sales has 14, Support has 3. A rectangular array would force every row to be the length of the longest one:

string[,] teams = new string[3, 14]; // wastes 8 empty slots in Engineering, 11 in Support

That wastes memory and, worse, makes empty slots ambiguous — is an empty slot "no employee here" or a genuine data error? The rectangle lies about the true shape of the data.

The Solution

A jagged array lets each row be exactly the size it needs to be — no wasted space, no fake "empty" entries:

string[][] teams = [ ["Amy", "Ben", "Cara", "Dev", "Elle", "Finn"], // Engineering: 6 ["Gia", "Hugo", "Ivy", "Jax", "Kim", "Leo", "Moe", "Nia", "Omar", "Pia", "Quin", "Rex", "Sam", "Tia"], // Sales: 14 ["Uma", "Vik", "Wes"], // Support: 3 ];

Big Picture

A JAGGED ARRAY — string[][]
teams[0]
Amy
Ben
Cara
length 3
teams[1]
Gia
Hugo
Ivy
Jax
Kim
length 5 (…and more)
teams[2]
Uma
Vik
length 2 (…and more)
teams is one outer array of 3 rows. Each row is its own independent array, with its own length. No wasted slots, no forced uniformity.

How It Works

BUILDING A JAGGED ARRAY, STEP BY STEP
1. CREATE THE OUTER ARRAY
int[][] triangle = new int[4][]; // 4 rows — but each row is still null right now
2. CREATE EACH INNER ARRAY SEPARATELY
triangle[0] = [1];
triangle[1] = [1, 2];
triangle[2] = [1, 2, 3];
triangle[3] = [1, 2, 3, 4];
3. OR INITIALIZE IT ALL AT ONCE
int[][] triangle2 =
[
    [1],
    [1, 2],
    [1, 2, 3],
    [1, 2, 3, 4],
];
4. READ, WRITE, AND ITERATE
int value = triangle[2][1];   // row 2, then index 1 within that row → 2

for (int r = 0; r < triangle.Length; r++)
{
    for (int c = 0; c < triangle[r].Length; c++)   // each row asks ITS OWN Length
        Console.Write($"{triangle[r][c]} ");
    Console.WriteLine();
}

Simple Example

int[][] triangle = [ [1], [1, 2], [1, 2, 3], [1, 2, 3, 4], ]; for (int r = 0; r < triangle.Length; r++) { foreach (int n in triangle[r]) Console.Write($"{n} "); Console.WriteLine(); } // 1 // 1 2 // 1 2 3 // 1 2 3 4

Code → Meaning → Result:

Real-World Example

A company's org chart tool needs to print every department along with its members. Departments genuinely have different headcounts, so a jagged array models this honestly.

string[] departmentNames = ["Engineering", "Sales", "Support"]; string[][] departmentMembers = [ ["Amy", "Ben", "Cara", "Dev", "Elle", "Finn"], ["Gia", "Hugo", "Ivy", "Jax", "Kim", "Leo", "Moe", "Nia", "Omar", "Pia", "Quin", "Rex", "Sam", "Tia"], ["Uma", "Vik", "Wes"], ]; for (int d = 0; d < departmentNames.Length; d++) { string[] members = departmentMembers[d]; Console.WriteLine($"{departmentNames[d]} ({members.Length} people):"); foreach (string name in members) Console.WriteLine($" - {name}"); } // Engineering (6 people): // - Amy // - Ben // - Cara // - Dev // - Elle // - Finn // Sales (14 people): // - Gia // ... // Support (3 people): // - Uma // - Vik // - Wes

Notice how naturally members.Length reports the true size of each department — 6, 14, and 3 — with no wasted memory and no ambiguous "empty seat" values to filter out.

Analogy

A Bookshelf With Uneven Shelves

Picture a bookshelf with 4 shelves. Shelf 1 holds a single thick encyclopedia. Shelf 2 holds 6 paperbacks. Shelf 3 holds 20 thin comic books. Shelf 4 is completely empty. The shelf unit is the outer array — it has 4 fixed slots, one per shelf. But what's on each shelf is its own independent collection, unrelated in size to any other shelf.

That's a jagged array: a fixed number of "shelves" (the outer array), each holding its own independently-sized inner array. A rectangular array, by contrast, would be a shelf unit where every shelf is legally required to hold the exact same number of books, whether or not you actually have that many.

Under the Hood

HOW A JAGGED ARRAY IS STORED
1. THE OUTER ARRAY HOLDS REFERENCES, NOT VALUES
2. MULTIPLE SEPARATE ALLOCATIONS, NOT ONE BLOCK
3. AN OUTER SLOT CAN BE NULL

Common Confusion

Jagged array vs rectangular array — the syntax difference is tiny, the meaning is huge

FeatureRectangular T[,]Jagged T[][]
Row lengthsAll equal, alwaysIndependent — each row its own length
Memory layoutOne contiguous blockMultiple separate arrays (heap allocations)
Access syntaxgrid[r, c] — one indexer callgrid[r][c] — two separate indexer calls
Size checkGetLength(0)/GetLength(1)grid.Length for rows, grid[r].Length per row
Can a row be null?No — cells default to 0/null but rows always existYes — an unassigned row is null until set

Common Mistakes

Mistake 1 — Forgetting to initialize each inner row

Wrong — this throws NullReferenceException:

int[][] rows = new int[3][]; rows[0][0] = 5; // rows[0] is still null — you never gave it an inner array

Correct — create the inner array first:

int[][] rows = new int[3][]; rows[0] = new int[5]; rows[0][0] = 5; // works now

Mistake 2 — Using a shared column count in the inner loop

Wrong — assumes every row has the same length, defeating the point of a jagged array:

for (int c = 0; c < triangle[0].Length; c++) // uses row 0's length for every row Console.Write(triangle[3][c]); // will miss elements or throw, depending on shape

Correct — always ask each row for its own length:

for (int c = 0; c < triangle[3].Length; c++) Console.Write(triangle[3][c]);

Mistake 3 — Reaching for a jagged array when the data is actually uniform

If every row genuinely is the same length, a jagged array adds unnecessary complexity and extra heap allocations compared to a simple rectangular array. Only use a jagged array when row lengths genuinely differ.

When Should I Use It?

Use a jagged array when

Reach for something else when

Mental Model

Jagged array = an array of arrays, each row independent
T[][] = two separate bracket pairs, no comma
grid[r][c] = "go to row r's array, then index c inside it"

Remember:
· Each row is its own array — different lengths are not just allowed, they're the whole point.
· Rows start as null — you must assign each one before using it.
· Use rectangular arrays when rows are uniform; use jagged arrays when they aren't.

Key Takeaway


Check Your Understanding

You've seen how jagged arrays let each row have its own independent length. Time to check your understanding.

1. What is the defining feature of a jagged array compared to a rectangular array?

Show answer

Correct: B

Why B is correct: A jagged array (T[][]) is an array whose elements are themselves arrays. Each of those inner arrays is created and sized independently, so different rows can have different lengths — unlike a rectangular array, where every row is forced to be the same length.

Why A is incorrect: Jagged arrays work with any type — int[][], Product[][], and so on, not just strings.

Why C is incorrect: Rectangular arrays are typically faster for uniform data, since they're one contiguous block rather than several scattered allocations.

Why D is incorrect: Jagged arrays can be modified after creation — you can reassign an entire row to a new array, or change individual elements.

Reinforcement: The whole point of a jagged array is independent row lengths — that's what "jagged" (uneven) refers to.

2. After running int[][] rows = new int[3][];, what is rows[0] before you assign anything to it?

Show answer

Correct: B

Why B is correct: new int[3][] only allocates the outer array — 3 slots that will each eventually hold a reference to an inner int[]. Until you explicitly assign one, each slot's default value (like any uninitialized reference-type slot) is null.

Why A is incorrect: No inner array has been created yet at all — there's no empty array sitting there, just a null reference.

Why C is incorrect: Nothing was sized to 3 elements — the outer array has 3 slots, but each slot's own inner array hasn't been created.

Why D is incorrect: This code compiles and runs fine; the null reference only becomes a problem if you try to index into rows[0] before assigning it.

Reinforcement: Creating the outer jagged array does not create any inner arrays — that's always a separate step.

3. An org-chart tool needs to store each department's list of employees, where department sizes range from 2 to 40 people. Which structure best fits, and why?

Show answer

Correct: B

Why B is correct: Department sizes vary wildly (2 to 40) — a jagged array lets each department's inner array be exactly the right size, with no wasted or ambiguous slots.

Why A is incorrect: A rectangular array sized to the biggest department (40) would waste up to 38 unused slots per smaller department, and you'd need a separate way to know how many entries in each row are "real."

Why C is incorrect: Mixing everyone into one flat array loses the grouping by department entirely — you'd have no way to tell where one department ends and the next begins.

Why D is incorrect: The two structures behave very differently here — one wastes memory and needs extra bookkeeping, the other represents the true shape of the data directly.

Reinforcement: Jagged arrays exist precisely for data where "row length" is genuinely variable and meaningful, like department headcounts.

4. Why is a jagged array typically less cache-friendly than a rectangular array of the same total size?

Show answer

Correct: B

Why B is correct: The outer array of a jagged array holds references to independently allocated inner arrays. Those inner arrays can end up anywhere on the heap, unlike a rectangular array's single unbroken memory block — so a rectangular array is generally more compact and faster to scan sequentially.

Why A is incorrect: Arrays store whatever type you declare them with (e.g. int), not text — this has nothing to do with memory layout.

Why C is incorrect: Neither array type sorts its contents automatically.

Why D is incorrect: There is a real, well-known performance difference for uniform data — it's one of the trade-offs to weigh when choosing between the two.

Reinforcement: Flexibility (independent row lengths) comes at the cost of memory locality — that's the core trade-off between jagged and rectangular arrays.

You now know both flavors of multidimensional data in C# — rectangular for uniform grids, jagged for irregular ones. Next, you'll meet a collection that can grow and shrink on its own: List<T>.


dotnetmadeeasy.com — Learn C# and .NET, the right way.