Why is enumerating over List faster than IList?

6/2/2025
2 minute read

If we have a List<T> and an IList<T>, enumerating over the List<T> is faster than enumerating over the IList<T>. Why is that?

Benchmark Code

[MemoryDiagnoser]
public class Benchmark
{
    private readonly List<int> numbersList = Enumerable.Range(0, 10_000).ToList();
    private readonly IList<int> numbersIList = Enumerable.Range(0, 10_000).ToList();

    [Benchmark(Baseline = true)]
    public int GetSumOfList()
    {
        var sum = 0;
        foreach (var number in numbersList) { sum += number; }
        return sum;
    }

    [Benchmark]
    public int GetSumOfIList()
    {
        var sum = 0;
        foreach (var number in numbersIList) { sum += number; }
        return sum;
    }
}

We have a result like this:

| Method        | Mean      | Error     | StdDev    | Ratio | RatioSD | Allocated | Alloc Ratio |
|-------------- |----------:|----------:|----------:|------:|--------:|----------:|------------:|
| GetSumOfList  |  4.214 us | 0.0836 us | 0.0782 us |  1.00 |    0.03 |         - |          NA |
| GetSumOfIList | 19.508 us | 0.0459 us | 0.0384 us |  4.63 |    0.08 |      40 B |          NA |

40 bytes

Were are those 40 bytes coming from? When you call List<T>.GetEnumerator() (which will be done in every foreach loop) you get a struct named Enumerator. When calling IList<T>.GetEnumerator() you get a variable of type IEnumerator, which contains a boxed version of said enumerator. That explains the 40 bytes of allocation and some of the performance difference.

Virtual method calls

But there is a second dimension to this. When you call IList<T>.GetEnumerator(), it has to call a virtual method, which is slower than calling a non-virtual method. Basically, someone has to check which concrete type is really being used, and then call the correct method. This is not the case with List<T>, which is already the concrete type.

An error has occurred. This application may no longer respond until reloaded. Reload x