Back to Blog
C#

c# foreach with index: getting the iteration index

c# foreach with index: Learn how to get the current index inside a c# foreach loop using a counter, LINQ Select, or a for loop, and understand the tradeoffs.

foreachiteration indexlinqfor loopenumerable
Diagram showing a foreach loop with an index counter, LINQ Select, and for loop alternatives.

When you need the current index inside a c# foreach with index, the loop itself does not provide it. The foreach statement iterates over a collection without exposing the position of the current element. This article explains the common ways to obtain that index and the tradeoffs of each approach.

Why foreach Does Not Expose an Index

The foreach statement is designed to work with any type that implements IEnumerable<T>. It calls GetEnumerator and then repeatedly calls MoveNext and reads Current. The enumerator does not provide a public index property. That is why the loop syntax does not include an index variable. If you need the position, you have to derive it from the collection structure or use a different loop construct.

Using a Counter Variable

The simplest approach is to declare a counter before the loop and increment it after each iteration. This works with any collection type and does not require changing the loop syntax.

var items = new List<string> { "apple", "banana", "cherry" }; int index = 0; foreach (var item in items) { Console.WriteLine($"{index}: {item}"); index++; }

The counter is just a local variable. It starts at zero and increases by one after each iteration. This matches the natural zero-based indexing used by arrays and most collections. The main risk is forgetting to increment the counter, which would cause every iteration to see the same index. In a longer loop, that mistake can be hard to spot because the code still compiles and runs.

Using LINQ Select to Carry the Index

The LINQ Select method has an overload that passes the index to the selector function. You can combine that with a tuple to keep the item and its index together.

foreach (var (item, index) in items.Select((item, index) => (item, index))) { Console.WriteLine($"{index}: {item}"); }

This approach avoids a separate counter variable. The index is computed by the enumerator internally and passed to the selector. The resulting sequence is a tuple of (item, index). The foreach loop then deconstructs that tuple directly. This is more functional in style and can be useful when you need to transform the collection while also tracking position. However, it does add an extra layer of indirection. The Select method creates a new enumerable that wraps the original, and each iteration allocates a tuple. For most collections this overhead is negligible, but it is not free.

Using a for Loop Instead

If the collection supports direct indexing, a for loop is often the clearest way to get the index. Arrays, List<T>, and other types with an indexer can be used this way.

for (int i = 0; i < items.Count; i++) { var item = items[i]; Console.WriteLine($"{i}: {item}"); }

The for loop gives you explicit control over the index variable, the condition, and the increment. It is easy to read and does not rely on any extra allocation. The limitation is that not every collection implements an indexer. A LinkedList<T> or a custom IEnumerable<T> that does not expose an indexer cannot be used with this pattern. In those cases you would need to fall back to a counter or LINQ.

Performance and Allocation Considerations

The counter approach adds only an integer increment per iteration, which is essentially free. The LINQ Select approach creates an iterator and a tuple per element. The actual cost depends on the collection size and the .NET version. In modern .NET, the LINQ implementation is often optimized, but it still involves a delegate call and a tuple allocation for each item. If you are processing millions of items in a tight loop, the difference can matter. The for loop uses the indexer directly, which is usually as fast as a foreach loop for arrays and lists. The choice should be based on the collection type and the performance requirements of the surrounding code. Without measurements, it is safer to assume that the counter and for loop have lower overhead than the LINQ approach.

Maintainability and Readability Tradeoffs

The counter variable is easy to understand but easy to misuse. If the loop body is long, the increment line might be far from the top, making it less obvious that the index is being updated. The LINQ version is compact and keeps the index tied to the item, but it can be less familiar to developers who are not comfortable with tuple deconstruction. The for loop is explicit and widely understood, but it only works with indexable collections. Choose the approach that matches the team's style and the collection's capabilities. For a one-off script, the counter is usually fine. For a codebase where functional patterns are common, the LINQ version may fit better.

Edge Cases: Null and Empty Collections

A foreach loop throws a NullReferenceException if the collection is null. That is true regardless of whether you use a counter or LINQ. If you need to handle null, check it before the loop. An empty collection is not a problem; the loop simply does not execute. With a counter, the index variable remains at its initial value after the loop. With LINQ, the Select sequence is empty, so the foreach body never runs. With a for loop, the condition is false from the start, so the loop is skipped. These behaviors are consistent, but you should be aware of them when writing code that relies on the index after the loop.

Choosing the Right Approach

There is no single best way to get the index in a foreach loop. The decision depends on the collection type, the surrounding code, and the performance profile you need. Use a counter when you want the simplest possible solution and the collection does not support indexing. Use LINQ Select when you prefer a functional style and the collection is small or the overhead is acceptable. Use a for loop when the collection has an indexer and you want explicit control. The key is to be consistent within a codebase so that readers can predict how the index is obtained.

ApproachReadabilityApplicabilityOverhead
CounterHighAny IEnumerableMinimal
LINQ SelectModerateAny IEnumerableTuple allocation per item
For loopHighIndexable collectionsMinimal
c# foreach with index - Get the iteration index | RYUSLOG DEV