Back to Blog
C#

C# for vs foreach: Choosing the Right Loop

Compare C# for and foreach loops to choose the right iteration style: syntax, allocation behavior, collection types, and code examples.

C#loopsforeachfor loopiterationperformance
Illustration comparing C# for and foreach loops with a performance metric

Choosing between for and foreach in C# affects readability, memory allocation, and sometimes performance. For most everyday collection traversal, foreach is the idiomatic choice. But when you need index-based access or are working with value-type collections, for can be faster. Understanding the tradeoffs helps you pick the loop that fits the actual requirement.

Basic Syntax of for and foreach

The for loop uses an index variable, a condition, and an increment expression. It gives you full control over iteration order and lets you access elements by position.

int[] numbers = { 10, 20, 30, 40 }; for (int i = 0; i < numbers.Length; i++) { Console.WriteLine(numbers[i]); }

The foreach loop hides the index and works directly with the collection's enumerator. It is shorter and less error-prone because you do not manage the loop variable.

foreach (int number in numbers) { Console.WriteLine(number); }

Both loops produce the same output for this array. The difference becomes important when you consider how each is compiled and executed.

How foreach Works Under the Hood

When you write foreach over an array, the C# compiler generates code that uses an index-based loop internally, avoiding an enumerator allocation. For a List<T>, foreach uses a struct enumerator, which ordinarily avoids a heap allocation. For collections such as Dictionary<TKey, TValue>, the enumerator is also a struct, but iteration order is not guaranteed.

The key point is that foreach does not always mean slower. For arrays, the generated code is nearly identical to a manual for loop. For List<T>, the struct enumerator avoids heap allocation, but MoveNext() and the Current property are still accessed on each iteration. The JIT compiler can often inline those non-virtual calls, but not always.

Performance: Allocation and Indexing

The most significant performance differences show up when you iterate over value-type collections. Consider a List<int>:

List<int> list = new List<int> { 1, 2, 3, 4 }; // foreach foreach (int item in list) { // use item } // for for (int i = 0; i < list.Count; i++) { int item = list[i]; // use item }

In the foreach version, the compiler uses List<int>.Enumerator, a struct. Current returns the value directly. In the for version, you access the list's indexer, which also returns the value. A for loop evaluates list.Count on every iteration unless you cache it; the foreach enumerator keeps its own index and checks the list version internally.

For collections of reference types, the difference is usually negligible. The real cost appears when the collection is large and the loop body is small. In that case, the overhead of repeated method and property access can become measurable. A for loop with a cached length and direct index access avoids that overhead.

int count = list.Count; for (int i = 0; i < count; i++) { // direct index access }

This is a common optimization, but it is rarely necessary unless profiling shows a bottleneck.

When to Use foreach Over for

Use foreach when you do not need the index and you are iterating over a collection that implements IEnumerable<T>. This includes arrays, lists, dictionaries, sets, and custom collections. foreach is safer because it prevents off-by-one errors and makes the intent clear. It also works with LINQ and other lazy sequences.

Dictionary<string, int> scores = new Dictionary<string, int>(); foreach (KeyValuePair<string, int> pair in scores) { Console.WriteLine($"{pair.Key}: {pair.Value}"); }

foreach is the natural choice for read-only iteration. With versioned collections such as List<T>, it also prevents adding or removing items during iteration because the runtime throws an InvalidOperationException. That is often desirable because it prevents subtle bugs.

When to Use for Over foreach

Use for when you need the index for something other than simple access. For example, when you need to iterate in reverse, skip elements, or access multiple arrays in parallel.

for (int i = numbers.Length - 1; i >= 0; i--) { Console.WriteLine(numbers[i]); }

You also need for when you want to add or remove items during iteration. A foreach loop throws if the collection changes. A for loop gives you the control to adjust the index accordingly.

for (int i = 0; i < list.Count; i++) { if (list[i] < 0) { list.RemoveAt(i); i--; // adjust index } }

Another case is when you are working with a multidimensional array or a custom data structure where the indexer is the only access method. foreach would flatten a multidimensional array, which may not be what you want.

Modifying Collections During Iteration

A common mistake is trying to remove items from a List<T> (or another versioned collection) inside a foreach loop. The runtime throws an InvalidOperationException because the enumerator detects that the collection changed. This is a deliberate design to keep iteration safe.

foreach (int item in list) { if (item < 0) { list.Remove(item); // throws } }

To remove items while iterating, use a for loop and iterate backward. This avoids index-shifting issues.

for (int i = list.Count - 1; i >= 0; i--) { if (list[i] < 0) { list.RemoveAt(i); } }

This is a common pattern for in-place filtering. It is safe and does not require an extra copy of the collection.

Span and Memory Considerations

When working with Span<T> or Memory<T>, iteration is optimized to avoid allocation. foreach over a Span<T> uses a ref-based enumerator, so it avoids per-element copies. If you need to modify elements in place, you can use foreach (ref T item in span) in current C# versions; an indexed for loop is also a clear option and is required when the update depends on the index.

Span<int> span = stackalloc int[] { 1, 2, 3 }; for (int i = 0; i < span.Length; i++) { span[i] *= 2; }

If you need to update every element without using its index, a ref loop variable works for Span<T>:

foreach (ref int item in span) { item *= 2; }

For Memory<T>, use its Span property to apply the same logic.

Choosing Based on Collection Type

The collection type often dictates the better loop. For arrays, both loops compile to similar code, but foreach is slightly more concise. For List<T>, for with a cached count can be faster in tight loops. For IEnumerable<T> sequences that are lazily evaluated, foreach is the only choice because you cannot index into them.

The following table summarizes the key differences:

Collection Typeforeach Behaviorfor Behavior
ArrayIndex-based internally, no allocationDirect index access, requires manual bounds
List<T>Struct enumerator, no heap allocationIndexer access, may be faster with cached count
DictionaryStruct enumerator, unordered iterationNot applicable without keys
IEnumerable<T>Enumerator, lazy evaluationNot applicable
Span<T>Optimized ref-based enumeration; use foreach (ref T) to mutateIndexed access for mutation by index

For most application code, foreach is the better default because it is readable and safe. Use for when you need index control, modification during iteration, or when profiling shows a bottleneck. The performance difference is rarely significant unless you are iterating over millions of elements in a tight loop with minimal work per element.

When you do need maximum performance, measure first. The JIT compiler can optimize both loops differently depending on the collection and the surrounding code. A microbenchmark with realistic data will tell you whether the choice matters in your specific scenario.

c# for vs foreach: Practical Usage and Code Examples | RYUSLOG DEV