`List<T>` Geometric Growth & Array Reallocation Overhead
1Concept
`List<T>` wraps an internal `T[] _items` array. When capacity is exceeded, it allocates a new array of double size (`Capacity * 2`) and performs `Array.Copy`, triggering GC heap allocations.
2Architecture Diagram
List<T> Growth Timeline: Initial: [ _items (Capacity: 4) ] ──> Items: [ 1, 2, 3, 4 ] Add(5): [ Heap Alloc: new T[8] ] ──> Array.Copy ──> Old array becomes GC Garbage! Add(9): [ Heap Alloc: new T[16] ] ──> Array.Copy ──> Old array becomes GC Garbage!
3Code Example
C# 13 & .NET 9
using System;
using System.Collections.Generic;
public class ListGrowthDemo
{
public static void Main()
{
// Unoptimized (triggers multiple reallocations):
var listDynamic = new List<int>();
Console.WriteLine($"Default Initial Capacity: {listDynamic.Capacity}");
// Optimized (pre-sized capacity):
var listOptimized = new List<int>(10_000);
Console.WriteLine($"Pre-sized Initial Capacity: {listOptimized.Capacity} (Zero Array.Copy triggers!)");
}
}4Expected Output
Default Initial Capacity: 0 Pre-sized Initial Capacity: 10000 (Zero Array.Copy triggers!)
5Key Takeaways
- ✓Always pre-size `new List<T>(expectedCount)` when the approximate size is known.
- ✓Default capacity starts at 0, expanding to 4 on first `.Add()`, then doubling.
- ✓Use `CollectionsMarshal.AsSpan(list)` (.NET 6+) to iterate list elements without enumerator overhead.