How to Loop Through a List in C#: Every Way, Benchmarked
Khalid Abuhakmeh compares every practical way to loop through a List<T> in C#, from foreach and for to List.ForEach, Index() and spans, and benchmarks them on .NET 10 and .NET 11 RC 1. The post also covers safe removal during iteration and async loops. It is a useful reference for choosing the right loop in everyday and hot-path code.
Key points
- Use foreach by default; switch to for or Enumerable.Index() when you need the position.
- Calling Remove inside foreach throws InvalidOperationException; use RemoveAll or a backward for loop instead.
- In the benchmarks, for and foreach finished within 2% of each other at 1,000 and 100,000 items.
- CollectionsMarshal.AsSpan ran about 21-26% faster than for, but must not be used while the list changes.
- Do not pass async lambdas to List.ForEach; the loop returns before the awaited work finishes.
- Index() was about 5.6x slower at 100,000 items and allocated 104 bytes, so avoid it in hot loops.
Most C# developers loop over a list with foreach, but the index, item replacement and performance needs push them toward other forms. The author walks through foreach with break and continue, for loops, Enumerable.Index(), List.ForEach, reverse iteration, spans and async patterns. He also explains why removing items inside foreach throws and offers three fixes: RemoveAll, a backward for loop and iterating over a ToList() copy.
The benchmarks on .NET 10 and .NET 11 RC 1 show for and foreach performing almost identically, while CollectionsMarshal.AsSpan is roughly 20 percent faster on larger lists. List.ForEach and Index() cost noticeably more, the latter because it goes through LINQ's iterator. The span approach carries a caveat: it has no version check, so structural changes to the list during use can silently break results. The original post has the full benchmark code and table.
Real-world use
- Filtering out unwanted items from an order or product list with a single RemoveAll call.
- Speeding up a hot numeric processing loop with CollectionsMarshal.AsSpan when the list is not modified.
- Calling a remote API for each item in a service layer, choosing between sequential await and Parallel.ForEachAsync.
Why it matters
For everyday code foreach is enough, but modifying a list during iteration and passing async lambdas to List.ForEach are common mistakes that cause runtime exceptions or silent bugs. Knowing the measured cost of each loop helps when performance actually matters.
Related stories
- Collection Return Types in C#: Interfaces, Views, and SnapshotsNick Cosentino (Dev Leader)2 d ago
- PostSharp 2027.0 advice parameter binding removes boxing and allocations from aspectsMetalama / PostSharp BlogOct 23
- NAudio 3.3.0 Simplifies Windows-Only Target Frameworks in the Meta-PackageMark Heath (Sound Code)Oct 18