Last spring, a sales dashboard I was building for a US retail client crashed on one innocent line: regions[4]. The array held four regions, and index 4 simply doesn’t exist.
Here’s the quick answer. A C# array is a fixed-size, zero-indexed block of values of one type, such as string[] regions = ["West", "South"];. You read items by index and get the count from Length.
In this tutorial, I’ll take you from declaring your first C# array to the problems you’ll actually hit at work: adding values to a fixed-size array, checking for empty or null, multidimensional and jagged arrays, and that crash.
I ran every snippet as a .NET 10 file-based app with dotnet run, using 2026 sales figures in USD.
How to Declare and Initialize a C# Array
You’ve got three ways to create an array. Give it a size with new, list the values in braces, or use a collection expression in square brackets, which C# 12 added and .NET 10 supports out of the box.
using System.Globalization;
CultureInfo.CurrentCulture = new CultureInfo("en-US");
decimal[] sales = new decimal[3]; // size only
string[] managers = new string[2];
string[] regions = { "West", "South", "Midwest" };
int[] stores = [42, 35, 58, 27]; // C# 12+
sales[0] = 182_450.75m;
Console.WriteLine($"sales: {string.Join(", ", sales)}");
Console.WriteLine($"managers[0] is null: {managers[0] is null}");
Console.WriteLine($"regions.Length: {regions.Length}");
Console.WriteLine($"Last region: {regions[regions.Length - 1]}");
Console.WriteLine($"Total stores: {stores.Sum()}");
Output:
sales: 182450.75, 0, 0
managers[0] is null: True
regions.Length: 3
Last region: Midwest
Total stores: 162

Notice the default values. new decimal[3] gives you zeros, and new string[2] gives you null elements, even though the type isn’t marked nullable.
Arrays of any type work the same way, whether that’s bool[], a class like Customer[], or the bytes you get when you convert a string to a byte array in C#.
| Element type | Default value after new T[n] |
|---|---|
int, decimal, double | 0 |
bool | false |
char | '\0' |
string and other reference types | null |
DateTime (struct) | 01/01/0001 00:00:00 |
Older code sometimes uses Array.CreateInstance(typeof(int), 5). It still works, but you only need it when the element type isn’t known until runtime.
Access C# Array Elements: Index, Length, ^1 and Ranges
Indexes start at 0, so the last element sits at Length - 1. Since C# 8, you can write ^1 instead, which means “one from the end.” A range like [1..3] includes the start and excludes the end.
string[] states = ["California", "Texas", "Florida", "New York", "Illinois"];
Console.WriteLine($"states[1]: {states[1]}");
Console.WriteLine($"states[^1]: {states[^1]}");
Console.WriteLine($"states[^2]: {states[^2]}");
string[] middle = states[1..3]; // index 1 and 2, the end is exclusive
string[] firstThree = states[..3];
string[] lastTwo = states[^2..];
Console.WriteLine($"[1..3]: {string.Join(", ", middle)}");
Console.WriteLine($"[..3]: {string.Join(", ", firstThree)}");
Console.WriteLine($"[^2..]: {string.Join(", ", lastTwo)}");
middle[0] = "Arizona"; // a range is a copy
Console.WriteLine($"After editing the range, states[1] is still {states[1]}");
states[1] = "Georgia"; // change an element in place
Console.WriteLine($"Now states[1] is {states[1]}");
Output:
states[1]: Texas
states[^1]: Illinois
states[^2]: New York
[1..3]: Texas, Florida
[..3]: California, Texas, Florida
[^2..]: New York, Illinois
After editing the range, states[1] is still Texas
Now states[1] is Georgia
The key detail is the last two lines. A range on an array returns a new copy, so editing middle doesn’t touch states. Assigning to states[1] does change it.
When you need to find an item by value rather than position, see how to check if a string array contains a value.
Loop Through a C# Array with for and foreach
I pick the loop based on one question: do I need the index? If yes, it’s for. If I only need each value, it’s foreach, which reads cleaner and can’t go out of bounds.
using System.Globalization;
CultureInfo.CurrentCulture = new CultureInfo("en-US");
string[] months = ["Jan", "Feb", "Mar", "Apr"];
decimal[] sales = [182_450.75m, 167_980.20m, 201_315.00m, 194_620.40m];
for (int i = 0; i < sales.Length; i++) // need the index
Console.WriteLine($"{months[i]} 2026: {sales[i],12:C}");
decimal total = 0;
foreach (decimal amount in sales) // only need values
total += amount;
Console.WriteLine($"Total: {total,12:C}");
Console.WriteLine($"Average: {sales.Average(),12:C}");
Console.WriteLine($"Best: {sales.Max(),12:C}");
Output:
Jan 2026: $182,450.75
Feb 2026: $167,980.20
Mar 2026: $201,315.00
Apr 2026: $194,620.40
Total: $746,366.35
Average: $186,591.59
Best: $201,315.00

The for loop lines up two parallel arrays by index. I’ve covered the details of the for loop in C# and the foreach loop in C# separately.
Sum(), Average() and Max() come from LINQ, and they work on any array. File-based apps and new SDK projects include System.Linq through implicit usings.
Single-Dimensional vs Multidimensional vs Jagged Arrays in C#
Everything so far has been a single-dimensional array: one row of values. When your data is a grid, like regions by quarter, you’ve got two options.
int[,] quarterly = // Q1-Q4 2026 sales, $K
{
{ 540, 589, 601, 655 },
{ 499, 512, 561, 590 },
{ 403, 415, 434, 472 },
};
Console.WriteLine($"Rank {quarterly.Rank}, Length {quarterly.Length}");
Console.WriteLine($"Rows {quarterly.GetLength(0)}, cols {quarterly.GetLength(1)}");
Console.WriteLine($"Row 1, Q3: ${quarterly[1, 2]}K");
string[][] byRegion = // jagged: rows differ
[
["California", "Oregon", "Washington", "Nevada"],
["Texas", "Florida"],
["Ohio", "Illinois", "Michigan"],
];
foreach (string[] row in byRegion)
Console.WriteLine($"{row.Length} states: {string.Join(", ", row)}");
Output:
Rank 2, Length 12
Rows 3, cols 4
Row 1, Q3: $561K
4 states: California, Oregon, Washington, Nevada
2 states: Texas, Florida
3 states: Ohio, Illinois, Michigan
![C# multidimensional int[,] array of quarterly sales next to a jagged string[][] array of states by region in VS Code](https://aspdotnethelp.com/wp-content/uploads/2026/10/csharp-multidimensional-vs-jagged-array-vscode.png)
A multidimensional array uses commas, [,], and every row has the same width. You read cells with quarterly[row, column] and get each dimension’s size from GetLength().
A jagged array, [][], is an array of arrays. The West has four states and the South has two, so jagged fits. Each inner array starts as null until you assign it.
Single T[] | Multidimensional T[,] | Jagged T[][] | |
|---|---|---|---|
| Shape | One row | Fixed grid | Rows of different lengths |
| Access | a[i] | a[r, c] | a[r][c] |
Length means | Element count | All cells (rows x columns) | Number of rows |
Supports ^1 and ranges | Yes | No | Yes |
Walking a grid takes nested loops, the same pattern I use to print a diamond with nested loops.
How Do You Add Values to a C# Array?
Strictly speaking, you can’t. An array’s length is fixed when it’s created. Every “add” technique really builds a new, bigger array and copies the old values into it.
string[] states = ["Texas", "Florida"];
Array.Resize(ref states, states.Length + 1); // copies
states[^1] = "Ohio";
Console.WriteLine($"Resize: {string.Join(", ", states)}");
string[] more = states.Append("Georgia").ToArray();
Console.WriteLine($"Append: {string.Join(", ", more)}");
string[] spread = [.. more, "Arizona", "Utah"]; // C# 12+
Console.WriteLine($"Spread: {string.Join(", ", spread)}");
List<string> list = [.. spread]; // grows freely
list.Add("Colorado");
list.Remove("Texas");
Console.WriteLine($"List: {string.Join(", ", list)} ({list.Count})");
Output:
Resize: Texas, Florida, Ohio
Append: Texas, Florida, Ohio, Georgia
Spread: Texas, Florida, Ohio, Georgia, Arizona, Utah
List: Florida, Ohio, Georgia, Arizona, Utah, Colorado (6)

Array.Resize allocates a new array, copies the elements and replaces your reference, which is why it takes ref. Append().ToArray() and the [.. states, "Utah"] spread leave the original alone.
Each of these copies the whole array, so adding inside a loop gets slow fast. That’s the signal to switch to List<T>; my guide to dynamic arrays in C# shows the growth pattern.
You may also see the old non-generic ArrayList class in legacy code. Skip it for new work, since it stores everything as object.
How to Check if a C# Array Is Empty or Null
Empty and null aren’t the same thing. An empty array exists and has Length == 0. A null array variable points at nothing, and reading its Length throws NullReferenceException.
int[] noOrders = [];
int[]? notLoaded = null;
int[] fiveSlots = new int[5]; // five zeros, not empty
Console.WriteLine($"Length == 0: {noOrders.Length == 0}");
Console.WriteLine($"!Any(): {!noOrders.Any()}");
Console.WriteLine($"Array.Empty Length: {Array.Empty<int>().Length}");
Console.WriteLine($"fiveSlots empty: {fiveSlots.Length == 0}");
// notLoaded.Length would throw NullReferenceException
bool pattern = notLoaded is null or { Length: 0 };
bool coalesce = (notLoaded?.Length ?? 0) == 0;
Console.WriteLine($"null or empty (pattern): {pattern}");
Console.WriteLine($"null or empty (?. ?? 0): {coalesce}");
Console.WriteLine($"IsNullOrEmpty(fiveSlots): {IsNullOrEmpty(fiveSlots)}");
static bool IsNullOrEmpty<T>(T[]? a) => a is not { Length: > 0 };
Output:
Length == 0: True
!Any(): True
Array.Empty Length: 0
fiveSlots empty: False
null or empty (pattern): True
null or empty (?. ?? 0): True
IsNullOrEmpty(fiveSlots): False

My go-to is array is null or { Length: 0 }, or its mirror array is not { Length: > 0 } in a helper. Any() and Count() == 0 work too, but Length is a direct property read.
Also note fiveSlots: new int[5] isn’t empty, it’s five zeros. For other types, see how to check for null values in C#.
When a method has nothing to return, hand back Array.Empty<T>() or [] instead of null. Callers can then loop without a guard, and the same idea applies when you check if an object is empty or null.
Why Do I Get IndexOutOfRangeException on a C# Array?
This was my dashboard bug. Any index below 0 or at Length and above throws System.IndexOutOfRangeException. The usual cause is <= where you meant <.
string[] regions = ["West", "South", "Midwest", "Northeast"];
Console.WriteLine($"Length {regions.Length}, last index {regions.Length - 1}");
int i = 4; // safe: check first
if (i >= 0 && i < regions.Length)
Console.WriteLine(regions[i]);
else
Console.WriteLine($"Index {i} is out of range, skipping it");
for (int j = 0; j <= regions.Length; j++) // bug: <= instead of <
Console.WriteLine($"regions[{j}] = {regions[j]}");
Output:
Length 4, last index 3
Index 4 is out of range, skipping it
regions[0] = West
regions[1] = South
regions[2] = Midwest
regions[3] = Northeast
Unhandled exception. System.IndexOutOfRangeException: Index was outside the bounds of the array.
at Program.<Main>$(String[] args) in C:\DotNetDemo\ArraysDemo\OutOfRange.cs:line 11

The bounds check before regions[i] avoids the crash, and the loop prints indexes 0 through 3 before index 4 fails. Switching to foreach or regions[^1] removes the off-by-one risk entirely.
Don’t wrap array access in try/catch to hide this. Like a divide by zero exception, it’s a bug to fix, not a condition to catch.
Useful C# Array Methods: Sort, Reverse, IndexOf and Find
The static Array class handles the everyday jobs. Sort, Reverse and Fill change the array in place, while LINQ methods like Distinct() return new sequences.
using System.Globalization;
CultureInfo.CurrentCulture = new CultureInfo("en-US");
string[] states = ["Texas", "Ohio", "California", "Florida", "Arizona"];
int[] storeCounts = [58, 27, 42, 35, 42];
Array.Sort(states); // sorts in place, A to Z
Console.WriteLine($"Sorted: {string.Join(", ", states)}");
Array.Reverse(storeCounts); // reverses in place
Console.WriteLine($"Reversed: {string.Join(", ", storeCounts)}");
Console.WriteLine($"IndexOf Florida: {Array.IndexOf(states, "Florida")}");
Console.WriteLine($"BinarySearch Ohio: {Array.BinarySearch(states, "Ohio")}");
Console.WriteLine($"First name over 6 letters: {Array.Find(states, s => s.Length > 6)}");
int[] copy = (int[])storeCounts.Clone();
Array.Fill(copy, 0);
Console.WriteLine($"Copy after Fill: {string.Join(", ", copy)} | original: {string.Join(", ", storeCounts)}");
Console.WriteLine($"Distinct: {string.Join(", ", storeCounts.Distinct())} | Max: {storeCounts.Max()}");
Output:
Sorted: Arizona, California, Florida, Ohio, Texas
Reversed: 42, 35, 42, 27, 58
IndexOf Florida: 2
BinarySearch Ohio: 3
First name over 6 letters: Arizona
Copy after Fill: 0, 0, 0, 0, 0 | original: 42, 35, 42, 27, 58
Distinct: 42, 35, 27, 58 | Max: 58
BinarySearch only works on a sorted array, which is why it runs after Sort. Clone() gives you a shallow copy, so filling copy left the original untouched.
I’ve written step-by-step guides to sort an array in C# and reverse an array in C#, including descending and custom orders.
Two common follow-ups: remove duplicates from a C# array and find the second largest number in an array.
C# Array vs List<T> (and Arrays in ASP.NET Core)
Arrays work exactly the same in ASP.NET Core, Blazor, console apps and Azure Functions. A controller can return string[], and model binding fills a string[] parameter from repeated query values.
The real question is whether an array is the right type at all. Here’s how I decide:
Array T[] | List<T> | |
|---|---|---|
| Size | Fixed at creation | Grows and shrinks |
| Add / remove | Copy to a new array | Add, Remove, Insert |
| Count property | Length | Count |
| Multidimensional | Yes, T[,] | No (use List<List<T>>) |
Ranges [1..3] | Yes | No (use GetRange) |
| Best for | Known, fixed data; lookup tables; performance-critical code | Collections that change at runtime |
If you’re collecting items from a form or a database, start with a List<T> in C#. If you look values up by key, such as sales by state, a Dictionary in C# beats both.
Which C# Array Technique Should You Use?
Here’s the short version I’d give a teammate:
| You need to | Use |
|---|---|
| Create an array with values | int[] a = [1, 2, 3]; (or { 1, 2, 3 } before C# 12) |
| Get the last element | a[^1] |
| Get a slice | a[1..3] (returns a copy) |
| Add one item, once | Array.Resize or [.. a, item] |
| Add items repeatedly | List<T>, then ToArray() if needed |
| Check null or empty | a is null or { Length: 0 } |
| Avoid IndexOutOfRangeException | foreach, or i < a.Length |
When it’s time to print the results, turning a list into a comma-separated string works the same for arrays with string.Join.
If you’re still building your C# basics, these pair well with this guide:
- Convert a char array to a string in C#
- Tuples in C# for returning more than one value
- The while loop in C#
- C# interview questions for freshers, arrays included
Frequently Asked Questions
Can you change the size of an array in C#?
No. An array’s length is fixed when it’s created. Array.Resize looks like it resizes, but it allocates a new array, copies the elements and updates your variable. Use List<T> when the size changes often.
What is the default value of an array element in C#?
It’s the type’s default: 0 for numbers, false for bool and null for strings and other reference types. Microsoft documents this in the C# array reference on Microsoft Learn.
What’s the difference between int[,] and int[][] in C#?
int[,] is a multidimensional array, a fixed grid where every row has the same length. int[][] is a jagged array, an array of arrays where each row can have its own length.
How do I get the last element of a C# array?
Use array[^1] in C# 8 and later, or array[array.Length - 1] in older code. Both throw on an empty array, so check Length first.
Is an empty array the same as a null array in C#?
No. An empty array is a real object with Length 0, while a null array has no object behind it. array is null or { Length: 0 } checks both at once.
Can I use collection expressions like [1, 2, 3] for arrays?
Yes, starting with C# 12 (.NET 8 and later). int[] a = [1, 2, 3]; creates an array, [] creates an empty one, and [.. a, 4] spreads an existing array into a new one.
Bijay Kumar is a renowned software engineer, accomplished author, and distinguished Microsoft Most Valuable Professional (MVP) specializing in SharePoint. With a rich professional background spanning over 15 years, Bijay has established himself as an authority in the field of information technology. He possesses unparalleled expertise in multiple programming languages and technologies such as ASP.NET, ASP.NET MVC, C#.NET, and SharePoint, which has enabled him to develop innovative and cutting-edge solutions for clients across the globe. Read more…