Back to Blog
C#

C# for vs foreach: Choosing the Right Loop

c# for vs foreach: Compare C# for and foreach loops: performance, allocation, collection types, and when to choose each for cleaner, faster code.

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

c# for vs foreach requires a clear understanding of the core syntax, runtime behavior, and practical implementation patterns demonstrated in the examples below.

The choice between for and foreach in C# is not just about syntax. It affects readability, memory allocation, and how the runtime optimizes your iteration. 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 measurably faster. Understanding the tradeoffs lets you pick the loop that fits the actual requirement.

The Basic Syntax of for and foreach

The for loop requires an index variable, a condition, and an incrementor. It gives you full control over the 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. This is an optimization that avoids allocating an enumerator object. For a List<T>, however, foreach uses a struct enumerator that is allocated on the stack, so there is no heap allocation in the typical case. For other collections like Dictionary<TKey, TValue>, the enumerator is also a struct, but the 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 it still incurs a virtual call to MoveNext() and Current on each iteration. The JIT compiler can often inline these calls, but not always.

Performance Differences: Allocation and Indexing

The most significant performance difference appears 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 the list's enumerator, which is a struct. The Current property returns the value directly. In the for version, you access the list's indexer, which also returns the value. The difference is that the for loop reads the Count property on every iteration unless you cache it. The foreach loop caches the enumerator's state 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 MoveNext() 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. It does not allow you to modify the collection during iteration without throwing an InvalidOperationException. That is a feature, not a bug, 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 modify the collection during iteration. The foreach loop throws if you add or remove items. 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 the array, which may not be what you want.

Modifying Collections During Iteration

A common mistake is trying to remove items from a 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

If you are working with Span<T> or Memory<T>, foreach is supported but with some restrictions. For Span<T>, the compiler generates a ref-based loop that is highly optimized. However, you cannot use foreach to modify the span's elements directly; you need a for loop with an index.

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

foreach over a span gives you read-only access to the elements. If you need to update the values, for is the only option. This is a practical distinction when working with high-performance code that uses stack allocation or unmanaged memory.

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>Read-only iterationAllows element modification

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 in a hot loop. 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.