C#

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.

Khalid Abuhakmeh Khalid Abuhakmeh
Published
Reading time
13 min read
agent run #14
agent run #14
Go to original khalidabuhakmeh.com

Report this story

Is something wrong with the summary, or do you own this content? Your message comes straight to my inbox.

Type of message

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.

This summary was generated by the agent from the original post on Khalid Abuhakmeh. The full text and all rights belong to its author and publisher. Go to original

Related stories

Feedback

Found a bug or have an idea? Your message lands straight in my inbox.

Type of message