---
title: "Why Lists are not lowered to a while loop"
description: "Some while ago I was explaining what *[\"Lowering\"](https://steven-giesel.com/blogPost/69dc05d1-9c8a-4002-9d0a-faf4d2375bce/c-lowering)* is. And the interesting part is that `List` behaves differently like an array while also a `List` is backed by an array. But why?"
updated: 2025-07-21
tags: ["C#", ".NET", "Performance"]
canonical: "https://steven-giesel.com/blogPost/2c204a37-1af5-45a4-ac4b-439d2d8a02f0/why-lists-are-not-lowered-to-a-while-loop"
---

# Why Lists are not lowered to a while loop

Some while ago I was explaining what *["Lowering"](https://steven-giesel.com/blogPost/69dc05d1-9c8a-4002-9d0a-faf4d2375bce/c-lowering)* is. And the interesting part is that `List` behaves differently like an array while also a `List` is backed by an array. But why?

## Lowered Array

Here a small recap about the Array:
```csharp
var range = new[] { 1, 2 };

foreach(var item in range)
    Console.Write(item);
```

Gets lowered to:
```csharp
int[] array = new int[2];
array[0] = 1;
array[1] = 2;
int[] array2 = array;
int num = 0;
while (num < array2.Length)
{
    int value = array2[num];
    Console.Write(value);
    num++;
}
```

## Lowered List

While a list:

```csharp
var list = new List<int> { 1, 2 };

foreach(var item in list)
    Console.Write(item);
```

Get's lowered to:
```csharp
List<int> list = new List<int>();
list.Add(1);
list.Add(2);
List<int>.Enumerator enumerator = list.GetEnumerator();
try
{
    while (enumerator.MoveNext())
    {
        Console.Write(enumerator.Current);
    }
}
finally
{
    ((IDisposable)enumerator).Dispose();
}
```

## Why no array?

We know that a `List` is backed by an array that gets dynamically sized (if not here is a refresher: *["How does List work under the hood"](https://steven-giesel.com/blogPost/06e2c8e8-0119-4e8d-9227-3cb922a8c916/how-does-list-work-under-the-hood-in-net)*). So wouldn't it be beneficial from a performance point of view to use that array? Given that we can do: `list[1]` and so on? Furthermore, can't we just generalize that to every `IReadOnlyList<T>` as they always have an indexer?

Let's start with the list and why it isn't used and the reason is very simple: `foreach` has a safeguard that prevents you from modifiying the collection. Also here I wrote an article: [*"How does a List know that you changed it while enumerating it?"*](https://steven-giesel.com/blogPost/6c39fcc6-2218-44b9-8280-deadb995aac2/-how-does-a-list-know-that-you-changed-it-while-enumerating-it). Basically, the list has a version field that gets incremented everything you mutate the list. If the foreach sees that the version was changed during the enumeration you get an exception.
We would loss the semantics if we take the `while` approach like the Array. For Arrays that approach is safe as you can't remove items or clear the whole collection.

If we have a look at `IReadOnlyList<T>` not only might we run into the same problem (as many collection types do that so we would loss the version check) but we also add two more:
 1. The compiler has to know more specifics here. It has to know how to special case `IReadOnlyList<T>` which increases complexity.
 2. More importantly: `IReadOnlyList<T>` does not guarantee that you access a specific index via `O(1)` time or space complexity. You can implement the indexer as you want without giving guarantees. But developers assume, rightfully so, that accessing an index is something fast. For example: You can access a character of a `StringBuilder` with an index, but that isn't a `O(1)` operation necessarily. `StringBuilder` is basically a linked list where each node can hold up to 8000 characters.