Back to Blog
C#

C# List Insert: Syntax, Behavior, and Performance

c# list insert: Learn how to use List<T>.Insert in C# to add elements at a specific index, understand its O(n) shifting behavior, and know when to choose alternatives.

C#List<T>InsertCollectionPerformance
Illustration of inserting an element into a C# list at a specific index, showing the shifting of subsequent elements.

c# list insert requires a clear understanding of the core syntax, runtime behavior, and practical implementation patterns demonstrated in the examples below.

The List<T>.Insert method in C# inserts an element at a specified index, shifting all subsequent elements to the right. This is a common operation when you need to maintain an ordered collection with an element placed at a specific position. Unlike Add, which appends to the end, Insert gives you precise control over where the new element lands.

Inserting an Element at a Specific Index

The List<T>.Insert method is defined on the List<T> class and takes two parameters: the zero-based index at which to insert the item, and the item itself. The method returns void; it modifies the list in place. Here is a minimal example:

List<string> names = new List<string> { "Alice", "Charlie" }; names.Insert(1, "Bob"); // names is now: "Alice", "Bob", "Charlie"

In this example, "Bob" is inserted at index 1, pushing "Charlie" to index 2. The original elements after the insertion point are shifted right by one position.

Syntax and Basic Usage

The method signature is public void Insert(int index, T item). The index parameter must be between 0 and Count, inclusive. If index equals Count, the item is appended to the end, making Insert equivalent to Add in that case. If index is out of range, an ArgumentOutOfRangeException is thrown.

List<int> numbers = new List<int> { 1, 2, 3 }; numbers.Insert(0, 0); // Insert at the beginning numbers.Insert(numbers.Count, 4); // Insert at the end, same as Add

When inserting at the beginning, all existing elements shift right. This is a common pattern for building a reverse-ordered list, though it is not efficient for large lists because each insertion is O(n).

What Happens When You Insert

Internally, List<T> stores elements in an array. When you call Insert, the runtime must make room for the new element. It does this by shifting all elements from the insertion index to the end one position to the right. This shift is a memory copy operation that runs in O(n) time, where n is the number of elements after the insertion point.

If the internal array is already at full capacity, the list allocates a new, larger array and copies the entire contents before performing the shift. This allocation and copy add further overhead, though the amortized cost is still O(1) per Add operation, not per Insert. For Insert, the shifting cost dominates regardless of capacity.

Insert vs Add: Choosing the Right Method

Add appends an element to the end of the list and runs in amortized O(1) time. Insert runs in O(n) time because of the shifting. The choice between them depends on whether the position matters. If you always add elements in order, Add is the natural and efficient choice. Use Insert only when the element must appear at a specific index.

OperationTime ComplexityUse Case
AddAmortized O(1)Append to the end
InsertO(n)Place at a specific index

For example, maintaining a sorted list by inserting each new element at the correct position is a common pattern, but it becomes expensive for large lists. In such cases, consider using a SortedSet<T> or a LinkedList<T> if you need frequent middle insertions.

Performance and Memory Implications

The O(n) shifting behavior of Insert is the primary performance concern. Each insertion causes a contiguous block of memory to be moved, which is fast for small lists but becomes noticeable for large lists. If you perform many insertions at arbitrary positions, the total cost can be quadratic. This is often a hidden bottleneck in algorithms that build a list by inserting at the front.

Memory usage also matters. When the internal array needs to grow, the list allocates a new array and copies all elements. This temporarily doubles memory usage. If you know the final size, you can use the List<T>(int capacity) constructor to preallocate the array and avoid repeated resizing.

Handling Index Errors and Edge Cases

The Insert method throws an ArgumentOutOfRangeException if index is less than 0 or greater than Count. This is a common source of runtime errors, especially when the index is derived from user input or external data. Always validate the index before calling Insert, or wrap the call in a try-catch if the index is not guaranteed to be valid.

Another edge case is inserting into an empty list. If Count is 0, the only valid index is 0. Inserting at index 0 on an empty list works and is equivalent to Add. Inserting at any other index throws an exception.

InsertRange and Related Methods

List<T> also provides InsertRange(int index, IEnumerable<T> collection), which inserts multiple elements at a given index. This is more efficient than calling Insert in a loop because it shifts the existing elements only once, rather than once per inserted item. The collection is inserted in order, so the first element of the collection lands at the specified index.

List<int> list = new List<int> { 1, 4 }; list.InsertRange(1, new[] { 2, 3 }); // list is now: 1, 2, 3, 4

If you need to insert a large batch of items, prefer InsertRange over repeated Insert calls. The same O(n) shifting applies, but the constant factor is lower because the shift happens once.

When a LinkedList Is a Better Choice

If your application frequently inserts elements in the middle of a large collection, List<T>.Insert may not be the best data structure. A LinkedList<T> offers O(1) insertion at a known node, but it does not support index-based access. You must traverse the list to find the insertion point, which is O(n). The tradeoff depends on your access patterns.

Use List<T> when you need fast random access by index and insertions are rare or happen near the end. Use LinkedList<T> when you have a reference to a node and need to insert around it frequently. For most scenarios, List<T> is the pragmatic choice because of its cache-friendly array storage and lower memory overhead per element. The O(n) shift is acceptable for lists up to tens of thousands of elements, but beyond that, consider a balanced tree structure like SortedSet<T> if ordering is the primary concern.

c# list insert: Practical Usage and Code Examples | RYUSLOG DEV