Every Monday our Dallas sales manager asks the same question: which state sold the most last week? A foreach loop in C# answers it in a few lines, because it visits every item in an array, list or dictionary without you managing an index.
In this tutorial, I’ll build that sales-by-state report one step at a time, so you’ll see how the foreach loop in C# handles arrays, List<T>, dictionaries, indexes, break and continue on .NET 10.
Here’s what we’re building. Five sales go in, the $0 order gets dropped, and the states come out ranked by revenue:

The program has three moving parts:
- A
Salerecord with the rep, the state and the amount. - A
foreachthat adds each sale into aDictionary<string, decimal>of state totals. - A second
foreachthat prints the totals with a rank number.
Before You Start the foreach Loop Build
You’ll need the .NET 10 SDK and an editor. I run every step as a single-file app with dotnet run File.cs in VS Code; if that’s new to you, start with how to create a console application in Visual Studio Code.
Every step uses this sample data, and only one thing changes per step:
| Rep | State | Amount | Note |
|---|---|---|---|
| Jenna Moore | TX | $1,250 | |
| Ethan Rodriguez | CA | $0 | Cancelled order |
| Ryan Carter | CA | $980 | |
| Chloe Brady | TX | $640 | |
| Zoe Ng | NY | $1,575 |
Each sale is a one-line positional record, record Sale(string Rep, string State, decimal Amount);. Amounts are decimal because it’s money, and the files that print dollars set the culture to en-US.
Step 1: Write a Basic foreach Loop Over an Array
The syntax is foreach (Type item in collection) { ... }. The loop asks the collection for each element in order and runs the body once per element:
System.Globalization.CultureInfo.CurrentCulture = new("en-US");
Sale[] sales = [
new("Jenna Moore", "TX", 1250m),
new("Ethan Rodriguez", "CA", 0m),
new("Ryan Carter", "CA", 980m),
new("Chloe Brady", "TX", 640m),
new("Zoe Ng", "NY", 1575m)];
foreach (Sale sale in sales)
{
Console.WriteLine(
$"{sale.Rep,-16} {sale.State} {sale.Amount,6:C0}");
}
record Sale(string Rep, string State, decimal Amount);
Output:
Jenna Moore TX $1,250
Ethan Rodriguez CA $0
Ryan Carter CA $980
Chloe Brady TX $640
Zoe Ng NY $1,575

Arrays work because they implement IEnumerable, and so does every collection type in .NET. My guide to arrays in C# covers declaring and filling them.
Note: foreach doesn’t actually need an interface. Per Microsoft’s reference, any type with a public GetEnumerator() whose result has Current and MoveNext() works, which is how Span<T> supports it.
Checkpoint: five rows, from Jenna Moore TX $1,250 down to Zoe Ng NY $1,575, including Ethan’s $0.
Step 2: Loop Through a List with var, continue and break
Now the sales live in a List<Sale>, and we keep a running total until the team hits its $2,500 weekly goal. I’ve switched to var, which lets the compiler infer Sale:
System.Globalization.CultureInfo.CurrentCulture = new("en-US");
List<Sale> sales = [
new("Jenna Moore", "TX", 1250m),
new("Ethan Rodriguez", "CA", 0m),
new("Ryan Carter", "CA", 980m),
new("Chloe Brady", "TX", 640m),
new("Zoe Ng", "NY", 1575m)];
decimal total = 0;
foreach (var sale in sales)
{
if (sale.Amount == 0) continue; // skip $0 orders
total += sale.Amount;
Console.WriteLine($"+{sale.Amount,6:C0} = {total:C0}");
if (total >= 2500m) break; // weekly goal met
}
Console.WriteLine($"Stopped at {total:C0}");
record Sale(string Rep, string State, decimal Amount);
Output:
+$1,250 = $1,250
+ $980 = $2,230
+ $640 = $2,870
Stopped at $2,870

continue jumps to the next element, and break leaves the loop entirely. Zoe’s $1,575 sale never gets read, because the goal was already met. The List in C# tutorial has more on adding, finding and sorting items.
Checkpoint: three + lines ($1,250, $980 and $640) and Stopped at $2,870.
Step 3: Iterate a Dictionary with foreach (KeyValuePair and Deconstruction)
Next we group sales by state. One loop fills a Dictionary<string, decimal>, and you can read it back two ways:
System.Globalization.CultureInfo.CurrentCulture = new("en-US");
List<Sale> sales = [
new("Jenna Moore", "TX", 1250m),
new("Ethan Rodriguez", "CA", 0m),
new("Ryan Carter", "CA", 980m),
new("Chloe Brady", "TX", 640m),
new("Zoe Ng", "NY", 1575m)];
var totals = new Dictionary<string, decimal>();
foreach (var sale in sales)
totals[sale.State] =
totals.GetValueOrDefault(sale.State) + sale.Amount;
foreach (KeyValuePair<string, decimal> pair in totals)
Console.WriteLine($"{pair.Key}: {pair.Value:C0}");
foreach (var (state, total) in totals)
Console.WriteLine($"{state} -> {total:C0}");
record Sale(string Rep, string State, decimal Amount);
Output:
TX: $1,890
CA: $980
NY: $1,575
TX -> $1,890
CA -> $980
NY -> $1,575

Each element of a dictionary is a KeyValuePair<TKey, TValue>. Since it has a Deconstruct method, var (state, total) splits it into two variables, which reads much better.
GetValueOrDefault returns 0 for a state we haven’t seen yet, so there’s no ContainsKey check. More tricks are in my dictionary in C# guide.
Checkpoint: TX $1,890, CA $980 and NY $1,575, printed twice in insertion order.
Step 4: Get the Index in a foreach Loop
A foreach doesn’t give you a position, but a report needs rank numbers. You’ve got two clean ways to get one:
List<Sale> sales = [
new("Jenna Moore", "TX", 1250m),
new("Ethan Rodriguez", "CA", 0m),
new("Ryan Carter", "CA", 980m),
new("Chloe Brady", "TX", 640m),
new("Zoe Ng", "NY", 1575m)];
// any .NET version: Select with an index
foreach (var (sale, i) in sales.Select((s, i) => (s, i)))
Console.WriteLine($"#{i + 1} {sale.Rep}");
// .NET 9 and later: Index()
foreach (var (i, sale) in sales.Index())
Console.WriteLine($"{i}: {sale.State}");
record Sale(string Rep, string State, decimal Amount);
Output:
#1 Jenna Moore
#2 Ethan Rodriguez
#3 Ryan Carter
#4 Chloe Brady
#5 Zoe Ng
0: TX
1: CA
2: CA
3: TX
4: NY

Index() is a LINQ method added in .NET 9 that returns (int Index, TSource Item) tuples. On older targets, Select((s, i) => (s, i)) builds the same tuple itself.
A plain int i = 0; counter with i++ at the end of the body also works. I only reach for it when the loop has a continue that shouldn’t count.
Checkpoint: #1 Jenna Moore to #5 Zoe Ng, then 0: TX to 4: NY. Index() starts at 0.
Step 5: Remove Items in a foreach Loop Without an Error
Ethan’s $0 order shouldn’t count, so the obvious move is to remove it inside the loop. Here’s what happens:
List<Sale> sales = [
new("Jenna Moore", "TX", 1250m),
new("Ethan Rodriguez", "CA", 0m),
new("Ryan Carter", "CA", 980m),
new("Chloe Brady", "TX", 640m),
new("Zoe Ng", "NY", 1575m)];
foreach (var sale in sales)
{
if (sale.Amount == 0)
sales.Remove(sale); // changes the list mid-loop
}
Console.WriteLine($"{sales.Count} sales left");
record Sale(string Rep, string State, decimal Amount);
Output (unhandled exception):
Unhandled exception. System.InvalidOperationException: Collection was modified; enumeration operation may not execute.
at System.Collections.Generic.List`1.Enumerator.MoveNext()
at Program.<Main>$(String[] args) in C:\DotNetDemo\ForeachDemo\Step5RemoveCrash.cs:line 8

A List<T> enumerator stays valid only while the list is unchanged. Microsoft’s docs say any add, change or delete invalidates it, and the next MoveNext throws InvalidOperationException.
The fix is to stop changing the list you’re enumerating. Loop over a copy, or let the list remove the items itself:
List<Sale> sales = [
new("Jenna Moore", "TX", 1250m),
new("Ethan Rodriguez", "CA", 0m),
new("Ryan Carter", "CA", 980m),
new("Chloe Brady", "TX", 640m),
new("Zoe Ng", "NY", 1575m)];
// Fix 1: loop over a copy, change the original
foreach (var sale in sales.ToList())
if (sale.Amount == 0) sales.Remove(sale);
Console.WriteLine($"ToList copy: {sales.Count} sales left");
// Fix 2: let List<T> do it in one call
List<Sale> again = [
new("Jenna Moore", "TX", 1250m),
new("Ethan Rodriguez", "CA", 0m),
new("Ryan Carter", "CA", 980m),
new("Chloe Brady", "TX", 640m),
new("Zoe Ng", "NY", 1575m)];
int removed = again.RemoveAll(s => s.Amount == 0);
Console.WriteLine($"RemoveAll: {removed} removed, {again.Count} left");
record Sale(string Rep, string State, decimal Amount);
Output:
ToList copy: 4 sales left
RemoveAll: 1 removed, 4 left
I use RemoveAll in the final report. It’s one call, and it returns how many items it removed.
Checkpoint: ToList copy: 4 sales left and RemoveAll: 1 removed, 4 left.
Test the Finished foreach Sales Report
Here’s the full program from the screenshot at the top. It removes the $0 sale, builds the totals with one foreach, then ranks them with Index() and nested deconstruction:
System.Globalization.CultureInfo.CurrentCulture = new("en-US");
List<Sale> sales = [
new("Jenna Moore", "TX", 1250m),
new("Ethan Rodriguez", "CA", 0m),
new("Ryan Carter", "CA", 980m),
new("Chloe Brady", "TX", 640m),
new("Zoe Ng", "NY", 1575m)];
sales.RemoveAll(s => s.Amount == 0);
var totals = new Dictionary<string, decimal>();
foreach (var sale in sales)
totals[sale.State] =
totals.GetValueOrDefault(sale.State) + sale.Amount;
var ranked = totals.OrderByDescending(t => t.Value);
foreach (var (rank, (state, total)) in ranked.Index())
Console.WriteLine($"{rank + 1}. {state} {total,7:C0}");
Console.WriteLine($"All states: {totals.Values.Sum():C0}");
record Sale(string Rep, string State, decimal Amount);
Output:
1. TX $1,890
2. NY $1,575
3. CA $980
All states: $4,445
var (rank, (state, total)) unpacks the Index() tuple and the KeyValuePair inside it in one go. TX wins with $1,890 from two sales, and the four real sales add up to $4,445.
The ranking itself comes from LINQ’s OrderByDescending. If you’d rather sort plain values, see how to sort an array in C#.
Can You Change the Item Inside a foreach Loop?
Not by assigning to the loop variable. It’s read-only, and the compiler stops you:
int[] quotas = [1000, 1500, 2000];
foreach (int quota in quotas)
quota += 100;
Build output:
C:\DotNetDemo\ForeachDemo\AssignLoopVariable.cs(3,5): error CS1656: Cannot assign to 'quota' because it is a 'foreach iteration variable'
The build failed. Fix the build errors and run again.
When the collection can hand out references, a ref iteration variable writes straight into it. Arrays can through AsSpan():
int[] quotas = [1000, 1500, 2000];
foreach (ref int quota in quotas.AsSpan())
quota += 100; // writes straight into the array
Console.WriteLine(string.Join(", ", quotas));
Output:
1100, 1600, 2100
Every quota went up by 100 in the original array. With a list of records, change a property on the object or build a new list instead; the loop variable itself stays read-only.
await foreach for Async Streams
When the data arrives over time, like pages from a web API, the method returns IAsyncEnumerable<T> and you consume it with await foreach. It’s the same pattern as the while loop in C# polling code, without the manual plumbing:
await foreach (var state in LoadStatesAsync())
Console.WriteLine($"Loaded {state}");
static async IAsyncEnumerable<string> LoadStatesAsync()
{
foreach (var state in new[] { "TX", "CA", "NY" })
{
await Task.Delay(300); // pretend this is a web API call
yield return state;
}
}
Output:
Loaded TX
Loaded CA
Loaded NY
Each state printed about 300 ms after the one before it. The loop body runs as soon as each item is ready, not after the whole list loads.
foreach vs for vs List.ForEach in C#
All three can walk our five sales. Here they are side by side on the same list:
List<Sale> sales = [
new("Jenna Moore", "TX", 1250m),
new("Ethan Rodriguez", "CA", 0m),
new("Ryan Carter", "CA", 980m),
new("Chloe Brady", "TX", 640m),
new("Zoe Ng", "NY", 1575m)];
for (int i = 0; i < sales.Count; i++)
Console.Write($"{i}:{sales[i].State} ");
Console.WriteLine("<- for");
foreach (var sale in sales)
Console.Write($"{sale.State} ");
Console.WriteLine("<- foreach");
sales.ForEach(sale => Console.Write($"{sale.State} "));
Console.WriteLine("<- List.ForEach");
record Sale(string Rep, string State, decimal Amount);
Output:
0:TX 1:CA 2:CA 3:TX 4:NY <- for
TX CA CA TX NY <- foreach
TX CA CA TX NY <- List.ForEach
| foreach | for | List<T>.ForEach | |
|---|---|---|---|
| Works on | Any enumerable | Anything with an indexer | List<T> only |
| Index available | No (use Index()) | Yes | No |
| break / continue | Yes | Yes | No (it’s a lambda) |
| Remove items while looping | No | Yes, loop backward | No |
| await inside | Yes | Yes | Not safely |
I default to foreach for reading and use a for loop in C# when I need the index or must change the list by position. List.ForEach is fine for one-liners, but you can’t break out of it.
Troubleshooting foreach Loop Errors
NullReferenceException When the Collection Is null
A foreach over an empty list simply doesn’t run, but a foreach over null throws. Fall back to an empty collection with ??:
List<Sale>? imported = null; // e.g. a failed CSV import
int count = 0;
foreach (var sale in imported ?? [])
count++;
Console.WriteLine($"Imported {count} sales");
record Sale(string Rep, string State, decimal Amount);
Output:
Imported 0 sales
For more ways to guard against missing data, see how to check for null values in C#.
Collection Was Modified; Enumeration Operation May Not Execute
That’s the Step 5 error. It also shows up when another thread adds to the list while you loop, because enumerating a List<T> isn’t thread-safe.
Important: copying with ToList() or locking the list fixes the threading version too.
Where to Take It Next: Extend the foreach Report
Three upgrades I’d make before handing this report to the sales team:
- Load the sales from a CSV file and print them as one line with the trick in convert a list to a comma-separated string in C#.
- Replace the state codes with full names using a switch case in C# expression.
- Stream each region’s sales with
await foreachinstead of loading them all at once.
If you want to compare the other loops next, these are the ones I’d read:
- The do-while loop in C#, which always runs at least once
- Reverse an array in C# without a manual loop
- ArrayList in C# and why List<T> replaced it
- Hashtable in C# compared with Dictionary
Frequently Asked Questions
What is a foreach loop in C#?
It’s a statement that runs a block of code once for each element of a collection, such as an array, List<T> or Dictionary<TKey, TValue>. You don’t manage an index; the loop gets each element from the collection’s enumerator.
How do I get the index in a C# foreach loop?
On .NET 9 and later, use foreach (var (i, item) in list.Index()). On older versions, use list.Select((item, i) => (item, i)) or keep your own counter variable.
Can I remove items from a list inside a foreach loop?
No. Changing the list invalidates its enumerator and the next step throws InvalidOperationException. Loop over list.ToList(), call RemoveAll, or use a backward for loop.
Is foreach slower than a for loop in C#?
For arrays and List<T>, the difference is tiny because the compiler and the struct enumerator avoid extra allocations. Pick the loop that reads best and measure only when a profiler points at it.
Does foreach work on a null collection?
No. Microsoft’s iteration statements reference says a foreach over null throws NullReferenceException, while an empty collection just skips the body. Use collection ?? [] when the value can be null.
What is await foreach in C#?
It consumes an IAsyncEnumerable<T>, an async stream, and waits for each element as it arrives. You use it inside an async method or a top-level program.
Bijay Kumar is a renowned software engineer, accomplished author, and distinguished Microsoft Most Valuable Professional (MVP) specializing in SharePoint. With a rich professional background spanning over 15 years, Bijay has established himself as an authority in the field of information technology. He possesses unparalleled expertise in multiple programming languages and technologies such as ASP.NET, ASP.NET MVC, C#.NET, and SharePoint, which has enabled him to develop innovative and cutting-edge solutions for clients across the globe. Read more…