Phase 13 of 30 · Topic 13.1

`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.