C# Array: How to Declare, Use and Add Values to Arrays in C#

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
C# array declared with new, an array initializer and a collection expression, shown in VS Code with dotnet run output
Three ways to create a C# array, and the default values you get with new decimal[3]

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 typeDefault value after new T[n]
int, decimal, double0
boolfalse
char'\0'
string and other reference typesnull
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
C# array for loop and foreach loop totaling 2026 monthly sales in USD, VS Code terminal output
for pairs each month with its sales figure; foreach totals the array

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
int[,] is a fixed grid; string[][] lets each region hold a different number of states

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[][]
ShapeOne rowFixed gridRows of different lengths
Accessa[i]a[r, c]a[r][c]
Length meansElement countAll cells (rows x columns)Number of rows
Supports ^1 and rangesYesNoYes

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)
Add values to a C# string array with Array.Resize, Append().ToArray(), collection expression spread and List<string>
Array.Resize, Append, the spread operator and List all end up with a new array

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
Check if a C# array is empty or null using Length, Any(), Array.Empty and the is null or { Length: 0 } pattern
Length == 0 checks empty; the property pattern handles null and empty in one test

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
C# array IndexOutOfRangeException unhandled exception in the VS Code terminal caused by an off-by-one for loop
The loop runs one step too far and regions[4] throws IndexOutOfRangeException

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>
SizeFixed at creationGrows and shrinks
Add / removeCopy to a new arrayAdd, Remove, Insert
Count propertyLengthCount
MultidimensionalYes, T[,]No (use List<List<T>>)
Ranges [1..3]YesNo (use GetRange)
Best forKnown, fixed data; lookup tables; performance-critical codeCollections 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 toUse
Create an array with valuesint[] a = [1, 2, 3]; (or { 1, 2, 3 } before C# 12)
Get the last elementa[^1]
Get a slicea[1..3] (returns a copy)
Add one item, onceArray.Resize or [.. a, item]
Add items repeatedlyList<T>, then ToArray() if needed
Check null or emptya is null or { Length: 0 }
Avoid IndexOutOfRangeExceptionforeach, 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:

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.