Updated on
A C# array’s length is fixed the moment we create it. We can add a value to a slot the array already has, and we can append one by building a new, longer array from an old one, but we cannot make an existing array bigger.
That single fact splits every technique on this page into two groups. Writing into an existing slot costs nothing. Producing a longer array allocates a new one and copies everything across.
Knowing which group we are in is the whole decision, so we start there, then work through both, and finish with a benchmark that puts numbers on the difference.
Let’s dive in.
VIDEO: Different Ways to Populate Arrays in C#.
Can We Add a New Value to an Existing C# Array?
No. A C# array’s length is fixed when we create it, and nothing in the framework changes the length of an array that already exists.
Every technique on this page is therefore one of exactly two things.
The first is writing a value into a slot the array already has. Both array[0] = 100 and array.SetValue(100, 0) do this. Nothing grows: a position that held 0 now holds 100, and no memory is allocated.
The second is building a brand new array of the size we want and copying the old contents into it. Array.Resize(), CopyTo(), Concat().ToArray() and collection expressions all do this, and every one of them allocates.
Which group we are in decides everything else. When we know the size up front, the first kind is free. When we do not, and values keep arriving one at a time, the second kind copies the entire array on every single addition, and a List<T> is the right answer instead.
Microsoft’s own C# language reference states it flatly: the length of each dimension is established when the array instance is created, and “You can’t change these values during the lifetime of the instance.”
If the type itself is new to us, the tour of arrays in C# covers declaration and initialization first.
How Do We Set a Value at a Given Index in C#?
Using this approach, we can set the value using the specified index or position of an array.
Let’s create a simple integer array of size 3:
var array = new int[3];
In this array, we have 3 indexes namely 0, 1, and 2.
In order to set the value at these indexes, we can use the index initializer:
array[0] = 100; array[1] = 101; array[2] = 102;
As we can see, each statement uses an array index to set its value.
However, it is also important to note that if we use any invalid index, then we are thrown the IndexOutOfRangeException. Hence, we need to ensure the index is between 0 and array.Length - 1.
Why Is Array.SetValue() Slower Than the Index Initializer?
Because SetValue() takes an object, so every value type we hand it gets boxed.
The signature is SetValue(object value, int index). Passing an int allocates a small heap object to carry that int, and the method unboxes it again to store it. The index initializer compiles to a direct write into the array’s memory and allocates nothing at all.
The cost is easy to measure. Filling 1,000 slots through the index initializer allocates 4,024 bytes, which is the array and nothing else. Filling the same 1,000 slots through SetValue() allocates 28,024 bytes: the same array, plus exactly 24 bytes for every one of the thousand boxes.
SetValue() earns its place when the element type is not known until run time, because it accepts anything. That flexibility is also its trap. Passing a value of the wrong type compiles cleanly and throws InvalidCastException when it runs.
Here is the method in use:
array.SetValue(value: 100, index: 0); array.SetValue(value: 101, index: 1); array.SetValue(value: 102, index: 2);
We can directly invoke the SetValue() method on an array itself. Please note that we need to provide the value as the first parameter and the index as the second parameter. Apart from this, there are multiple overloads available for different use cases.
Again, this method also throws IndexOutOfRangeException for incorrect array index.
How Do We Fill a C# Array From a Collection We Already Have?
We allocate an array of the right size and copy into it. The framework gives us four ways to spell that, and they do the same work.
source.CopyTo(destination, 0) copies every element into an array we made ourselves, starting at the index we name. The destination has to be big enough: a source that does not fit throws ArgumentException.
list.ToArray() walks a List<T> and hands back a new array sized to its Count.
first.Concat(second).ToArray() joins two sequences and materialises the result, which is the one to reach for when the two halves come from different places.
int[] copy = [.. source] is the collection expression form, available since C# 12. It is the shortest of the four and produces the same new array.
All four allocate exactly one array and copy every element into it, and none of them changes the source.
Array.CopyTo() Method
The first approach to adding values to an array is by using the Array.CopyTo() method.
Using this method, we can copy all the elements from one array to another array starting at the specified target array index.
To demonstrate this, let’s declare our first array with size 3:
var array1 = new int[3];
After that, let’s create our second array with some elements:
var array2 = new int[] { 100, 101, 102 };
Now, in order to copy all the elements from array2 to array1, we can invoke the CopyTo() method directly on the source array:
array2.CopyTo(array1, 0);
Here, the first parameter is the target array and the second parameter is the starting index of the target array. This method overwrites the elements on the target array. Another important factor is that the target array should have enough size to hold the copied elements.
There’s also another method Array.Copy() that is similar to Array.CopyTo(). The difference is that it copies a range of elements from the source to the target. When the job is copying array elements into a new array rather than filling one we already hold, that article works through the options side by side.
List<T> and the ToArray() Method
Another approach that we can use is the List<T> class. We can add elements to the list and then convert it to an array.
Let’s define a simple list of integers using the List<T> class:
var list = new List<int>();
Once we have the list object in place, we can add elements to it using the Add() method:
list.Add(100); list.Add(101); list.Add(102);
Finally, we can convert the list to an array using ToArray() method:
var array = list.ToArray();
This method works well for scenarios we don’t know the size of the array upfront.
LINQ Concat() and the ToArray() Method
Next on, there is the Concat() method in LINQ. This method concatenates two sequences into a single sequence.
Let’s say we have an array and we need to concatenate all the values from another array, then we can use this method.
To give an example, let’s create our first array with an empty size:
var array1 = Array.Empty<int>();
That call is the conventional way of getting a zero-length array, and Array.Empty<T>() and the other ways to declare an empty array are worth knowing before we use it.
Our second array contains some values:
var array2 = new int[] { 100, 101, 102 };
Now, let’s concatenate the two arrays into a single one:
array1 = array1.Concat(array2).ToArray();
Here, we’ve used the Concat() method to concatenate two arrays and then we invoked the ToArray() method to convert it back to an array. When the subject is joining two arrays into a single one rather than filling an array from a collection, that article compares the approaches built for it.
How Do We Append a Value to a C# Array?
We build a longer array and copy everything into it, because there is no other option. Two forms do it in one line.
Array.Resize(ref array, array.Length + 1) is the framework’s own method, and its name is misleading. Nothing is resized. Microsoft’s documentation describes what it actually does: it allocates a new array, copies the elements over, and then replaces the old array with the new one.
int[] longer = [.. array, value] is the collection expression form. It says the same thing in less code and allocates the same single array.
The catch is the same for both. Appending one value to a 1,000-element array copies 1,000 elements, so doing it a thousand times copies half a million.
When values arrive one at a time and we do not know how many are coming, the answer is not an array at all. Add them to a List<T>, then call ToArray() once at the end.
The ref is the part readers get wrong, and it is worth a code block. Because Array.Resize takes its array by reference, it repoints our variable and nothing else. Any other variable still holding the original array keeps the original array, at the original length:
var numbers = new[] { 100, 101, 102 };
var alias = numbers;
Array.Resize(ref numbers, 4);
numbers[3] = 103;
Console.WriteLine(numbers.Length); // 4
Console.WriteLine(alias.Length); // 3
And the collection expression form, which is the one to reach for in new code:
int[] numbers = [100, 101, 102]; int[] longer = [.. numbers, 103];
That form is newer than everything else on this page, and we cover the collection expression syntax in full in its own article.
When values keep arriving and the count is unknown, reach for List<T> and everything it can do instead, and convert once at the end.
The picture is worth more than the sentence: Resize leaves the old array exactly where it was.

That is every route covered, so here they are in one place, keyed to what we already hold: the call that fits it, and what that call costs:
| I have, and I want to | The call | What it costs |
|---|---|---|
| an array of the right size, and one slot to set | array[2] = 102; | Nothing. The slot already exists |
| the same, but the element type is only known at run time | array.SetValue(102, 2); | Boxes every value type: 24 extra bytes per call for an int |
a List<T>, and I want it as an array | list.ToArray(); | One new array, one copy |
| an array to fill from another array | source.CopyTo(destination, 0); | One copy into an array I allocated. Throws ArgumentException if the destination is too small |
| two sequences to join into one array | first.Concat(second).ToArray(); | One new array, one copy |
| a shorter way to write any of the copies above | int[] copy = [.. source]; | Identical: one new array, one copy. C# 12 and later |
| an array, and exactly one more value on the end | Array.Resize(ref array, array.Length + 1); or int[] longer = [.. array, value]; | A whole new array and a full copy, every time |
| values arriving one at a time, count unknown | list.Add(value);, then one list.ToArray() at the end | The list doubles its buffer as it grows, so it copies a handful of times instead of once per value |
Which Way of Adding Values to an Array Is Fastest?
Now, let’s dive into the interesting part which is performance comparison. We’ll use the BenchmarkDotNet library to do the benchmark for us.
The two groups measure different work, so their numbers are not a ranking. The manual group builds a thousand values from nothing. The populated group copies a thousand values that already exist, and the cost of producing them in the first place sits outside the measurement.
In this benchmark, we’ll use arrays of different sizes and then compare which approach performs the best in adding values to the array.
Let’s look at one of the approaches that we use for benchmarking:
public static int[] ArrayIndexInitializer(int arraySize)
{
var array = new int[arraySize];
for (var index = 0; index < arraySize; index++)
{
array[index] = index;
}
return array;
}
We assign the values to an array in a loop of varying sizes. The other approaches work similarly and you can check the source code if you’re interested.
It is important to mention that for the first two approaches, we have to populate the array manually using loops. But for other approaches that use already populated collections, we don’t have to do that. We simply use different methods to transfer elements from one collection to the array. Having this in mind, we will group our benchmark results.
Filling an Array of a Known Size
Now, let’s run the benchmark with BenchmarkDotNet at 1,000 and 10,000 elements and wait for the result:
| Method | Categories | ArraySize | Mean | Error | Allocated | |---------------------- |--------------------- |---------- |------------:|------------:|----------:| | ArrayIndexInitializer | Manual | 1000 | 474.9 ns | 25.56 ns | 3.93 KB | | ArrayIndexInitializer | Manual | 10000 | 4,066.1 ns | 154.97 ns | 39.09 KB | | SetValueMethod | Manual | 1000 | 6,561.5 ns | 241.15 ns | 27.37 KB | | SetValueMethod | Manual | 10000 | 58,206.3 ns | 1,158.23 ns | 273.46 KB | | | | | | | | | ArrayCopyTo | Populated Collection | 1000 | 161.1 ns | 3.29 ns | 3.93 KB | | LinqConcat | Populated Collection | 1000 | 162.5 ns | 5.82 ns | 3.93 KB | | CollectionExpression | Populated Collection | 1000 | 163.5 ns | 4.17 ns | 3.93 KB | | ListCollection | Populated Collection | 1000 | 164.9 ns | 4.57 ns | 3.93 KB | | ArrayCopyTo | Populated Collection | 10000 | 1,470.0 ns | 28.77 ns | 39.09 KB | | CollectionExpression | Populated Collection | 10000 | 1,586.1 ns | 44.29 ns | 39.09 KB | | LinqConcat | Populated Collection | 10000 | 1,594.9 ns | 40.09 ns | 39.09 KB | | ListCollection | Populated Collection | 10000 | 1,643.3 ns | 49.11 ns | 39.09 KB |
SetValueMethod is roughly fourteen times slower and allocates seven times the memory, and both numbers come from the same cause: boxing.
Inside the populated group the four approaches land within a tenth of each other at both sizes, and every one of them allocates exactly one array, which is the point. They are four ways of spelling one operation.
Growing an Array One Value at a Time
Everything above starts with an array that is already the right size. The queries that bring people to this page usually do not. So here is the comparison that matters when values arrive one at a time and the count is not known in advance: resizing the array on every value, against adding to a List<T> and converting once at the end. The first of these is the mistake, and the numbers say how big a mistake it is:
| Method | Count | Mean | Error | Allocated | |------------------- |------ |-------------:|------------:|-------------:| | ResizeInALoop | 1000 | 95.487 us | 4.0902 us | 1980.47 KB | | ListAddThenToArray | 1000 | 2.540 us | 0.0492 us | 12.16 KB | | ResizeInALoop | 10000 | 7,816.111 us | 156.4429 us | 195585.94 KB | | ListAddThenToArray | 10000 | 25.970 us | 0.5190 us | 167.41 KB |
At ten thousand values, resizing the array every time is just over three hundred times slower and allocates 1,168 times more memory: 191 MB against 167 KB. The reason is in the section above. Every Array.Resize copies the whole array, so building a 10,000-element array one value at a time copies about fifty million elements. List<T> doubles its buffer instead, so it grows thirteen times and copies about sixteen thousand elements in total.
Conclusion
An array’s length is fixed, so “adding a value” is really two different jobs. Setting a value in a slot that already exists is free, and the index initializer is the fastest way to do it. Producing a longer array always allocates a new one and copies, whether we spell it Array.Resize(), CopyTo(), Concat().ToArray() or [.. array, value].
The one thing worth remembering is what to do when we do not know how many values are coming: put them in a List<T> and call ToArray() once at the end. Resizing an array in a loop is the slow way to get the same result, by a factor of a few hundred. For the inverse operation, see removing elements from an array.
Tested with .NET 10.0.10 and BenchmarkDotNet 0.15.8.

Have you benchmarked in .Net 7? LINQ in .Net 7 is much, much faster.
Hi Paul. For the article, we used .NET 6. But yes, LINQ did get faster in .NET 7, so if you want, feel free to fork the source code, upgrade the projects and libraries and just run the benchmark. I would be really interested to see if the LINQ method goes up a bit. IT was a big difference in .NET 6 though.