while and do-while

A loop runs the same block again and again. The simplest is the while loop: it tests a bool condition, runs the block if it is true, and then tests again. When the condition is false, the program continues after the loop. Use it when you do not know in advance how many rounds you need.

decimal credit = 120.00m;
decimal monthlyCharge = 45.50m;
int months = 0;

while (credit >= monthlyCharge)
{
    credit -= monthlyCharge;
    months++;
    Console.WriteLine($"Month {months}: credit left {credit:F2}");
}

Console.WriteLine($"Prepaid credit lasts {months} months.");
Month 1: credit left 74.50
Month 2: credit left 29.00
Prepaid credit lasts 2 months.

Something inside the loop must eventually make the condition false, here the decreasing credit. Otherwise you have an infinite loop. Stop a runaway console program with Ctrl+C.

do-while

A do … while loop tests its condition after the block, so the block always runs at least once. That fits "ask, then check" situations such as input validation. Note the semicolon after the condition.

int reading;
string? input;

do
{
    Console.Write("Meter reading (kWh): ");
    input = Console.ReadLine();
}
while (!int.TryParse(input, out reading) || reading < 0);

Console.WriteLine();
Console.WriteLine($"Saved reading: {reading} kWh");
Meter reading (kWh): Meter reading (kWh): Meter reading (kWh):
Saved reading: 4120 kWh

The user typed abc, -5 and 4120. Typed input does not appear in the output above, so the three prompts end up on one line; in a real terminal each answer appears after its prompt. int.TryParse returns false for text that is not a number, which keeps the loop going (see out parameters for how it hands back the number).

The for loop

When you know how many rounds you need, the for loop keeps everything about the counting in one line: an initializer that runs once, a condition that is tested before every round, and an iterator that runs after every round. This is the C# form of a count-controlled loop.

for (int hour = 0; hour < 24; hour += 6)
{
    Console.WriteLine($"{hour:D2}:00 collect meter data");
}

for (int seconds = 3; seconds > 0; seconds--)
{
    Console.WriteLine($"Restarting in {seconds}...");
}
00:00 collect meter data
06:00 collect meter data
12:00 collect meter data
18:00 collect meter data
Restarting in 3...
Restarting in 2...
Restarting in 1...

The counter hour exists only inside the loop. Any of the three parts may be left out: for (;;) is an endless loop, usually ended with break.

Looping over an array by index

A for loop is the right tool when you need the position of each item, or need to visit only some positions. Arrays are numbered from 0, so the last valid index is Length - 1: the condition is i < readings.Length.

int[] readings = [412, 415, 419];

for (int i = 0; i <= readings.Length; i++)
{
    Console.WriteLine($"Reading {i}: {readings[i]}");
}
Reading 0: 412
Reading 1: 415
Reading 2: 419
Unhandled exception. System.IndexOutOfRangeException: Index was outside the bounds of the array.

foreach over collections

The foreach statement runs its block once for every item of a collection, in order, without a counter. You declare the type and name of the iteration variable, then in and the collection. It works on arrays, lists, dictionaries, sets, strings (one char at a time) and the result of a LINQ query.

string[] cities = ["Delft", "Leiden", "Gouda"];

foreach (string city in cities)
{
    Console.WriteLine($"Scheduling meter visits in {city}");
}

int digits = 0;
foreach (char c in "INC-20481")
{
    if (char.IsDigit(c))
    {
        digits++;
    }
}
Console.WriteLine($"{digits} digits");
Scheduling meter visits in Delft
Scheduling meter visits in Leiden
Scheduling meter visits in Gouda
5 digits

You may write var instead of the item type (foreach (var city in cities)); the compiler infers it from the collection. Looping over a dictionary gives you key-value pairs, shown on arrays, lists and collections.

The iteration variable is read-only

The iteration variable is a fresh copy of each item, and C# does not let you assign to it. This prevents the mistaken belief that changing it would change the array.

int[] readings = [412, 415, 419];

foreach (int r in readings)
{
    r = r + 1;
}
example.cs(5,5): error CS1656: Cannot assign to 'r' because it is a 'foreach iteration variable'

To change the items of an array, loop with for and assign through the index: readings[i] += 1. If the items are objects (instances of a class), you can still change their properties through the iteration variable, because it refers to the same object.

Do not change a list while looping over it

Adding or removing items of a List<T> during a foreach over that same list would leave the loop unsure which item comes next. .NET detects it and throws on the next step:

List<string> tickets = ["INC-20481", "INC-20482", "INC-20483"];

foreach (string ticket in tickets)
{
    if (ticket == "INC-20482")
    {
        tickets.Remove(ticket);
    }
}
Unhandled exception. System.InvalidOperationException: Collection was modified; enumeration operation may not execute.

Three safe alternatives: let the list do it with RemoveAll, loop backwards with for (removing an item then never shifts an item you still have to visit), or build a new list with the items you want to keep.

List<string> tickets = ["INC-20481", "INC-20482", "INC-20483"];
int removed = tickets.RemoveAll(t => t == "INC-20482");
Console.WriteLine($"{removed} removed: {string.Join(", ", tickets)}");

List<int> readings = [412, -1, 419, -1, 431];
for (int i = readings.Count - 1; i >= 0; i--)
{
    if (readings[i] < 0)
    {
        readings.RemoveAt(i);
    }
}
Console.WriteLine(string.Join(", ", readings));
1 removed: INC-20481, INC-20483
412, 419, 431

What foreach needs: IEnumerable

foreach is not limited to built-in collections. It works on any type that can hand out its items one at a time. In .NET that ability is described by the interface IEnumerable<T> (and its older, non-generic sibling IEnumerable): an object that can give you an enumerator, which in turn moves from item to item. Arrays, List<T>, Dictionary<TKey,TValue> and strings all implement it, and so does every LINQ query. A method that accepts an IEnumerable<T> parameter therefore accepts all of them.

You can write your own sequence with an iterator method: a method that returns IEnumerable<T> and uses yield return to hand out one item at a time. Like a Python generator function, it produces items lazily, only when the loop asks for the next one.

foreach (DateOnly day in WorkingDays(new DateOnly(2026, 10, 1), 4))
{
    Console.WriteLine($"{day:yyyy-MM-dd} {day.DayOfWeek}");
}

static IEnumerable<DateOnly> WorkingDays(DateOnly start, int count)
{
    DateOnly day = start;
    while (count > 0)
    {
        if (day.DayOfWeek is not (DayOfWeek.Saturday or DayOfWeek.Sunday))
        {
            yield return day;
            count--;
        }
        day = day.AddDays(1);
    }
}
2026-10-01 Thursday
2026-10-02 Friday
2026-10-05 Monday
2026-10-06 Tuesday

break and continue

Two statements change the course of a loop from inside it, as in most C-family languages (see break and continue in general):

  • break ends the loop immediately; the program continues after it.
  • continue skips the rest of the current round and starts the next one (in a for loop, the iterator still runs).
string[] queue = ["INC-20481", "", "INC-20482", "STOP", "INC-20483"];

foreach (string ticket in queue)
{
    if (ticket == "")
    {
        continue;  // skip empty lines
    }
    if (ticket == "STOP")
    {
        break;     // end of today's batch
    }
    Console.WriteLine($"Processing {ticket}");
}
Console.WriteLine("Batch done.");
Processing INC-20481
Processing INC-20482
Batch done.

Nested loops

A loop inside a loop runs completely for every round of the outer one. break only leaves the innermost loop it is in:

string[] substations = ["North", "South"];
int[][] loads = [[62, 97, 71], [55, 58, 60]];

for (int s = 0; s < substations.Length; s++)
{
    foreach (int load in loads[s])
    {
        if (load > 90)
        {
            Console.WriteLine($"{substations[s]}: overload {load}%, stop checking");
            break;
        }
        Console.WriteLine($"{substations[s]}: {load}% ok");
    }
}
North: 62% ok
North: overload 97%, stop checking
South: 55% ok
South: 58% ok
South: 60% ok

To leave several loops at once, put them in a method and use return, which is usually the clearest option. C# has no labeled break like JavaScript's yet; labeled break and continue are listed for C# 15, which is still in preview as of October 2026.

Indexes from the end and ranges

Loops often need "the last item" or "everything except the first". C# 8 added two small operators for that, which work on arrays, strings, List<T> and spans.

The index from end operator ^ counts from the back: ^1 is the last item, ^2 the one before it. So readings[^1] means the same as readings[readings.Length - 1], without repeating the variable name. ^0 points just past the end and is never a valid item.

The range operator .. selects a slice: start..end includes start and excludes end, so readings[1..3] has two items. Either side may be left out (..2 = the first two, 2.. = from index 2 to the end) and either side may count from the end (^2.. = the last two).

An array of five meter readings: 412, 415, 419, 431 and 440. Above each box is its index counted from the start, 0 to 4; below each box its index from the end, ^5 to ^1. The range 1..3 is marked under the second and third boxes, holding 415 and 419. index readings from end 0 1 2 3 4 412 415 419 431 440 ^5 ^4 ^3 ^2 ^1 readings[1..3] end excluded
Indexes from the start and from the end, and the range 1..3: it starts at index 1 and stops before index 3.
int[] readings = [412, 415, 419, 431, 440];

Console.WriteLine(readings[^1]);
Console.WriteLine(string.Join(" ", readings[1..3]));
Console.WriteLine(string.Join(" ", readings[^2..]));

// usage between consecutive readings: compare each with the previous one
foreach (int i in Enumerable.Range(1, readings.Length - 1))
{
    Console.Write($"{readings[i] - readings[i - 1]} ");
}
Console.WriteLine();

string ticket = "INC-20481";
Console.WriteLine(ticket[4..]);
440
415 419
431 440
3 4 12 9
20481

A range on an array, string or list creates a new array, string or list with copies of the items; changing the slice does not change the original. Enumerable.Range(start, count) produces count numbers starting at start, which is handy wherever a sequence of numbers is needed.

Loops or LINQ?

Many loops follow the same few shapes: filter some items, transform them, add them up. For those shapes .NET offers LINQ (Language Integrated Query), a set of methods such as Where, Select and Sum that work on every IEnumerable. Both halves of the next example compute the same total:

decimal[] usageKwh = [310.5m, 512.0m, 98.25m, 640.0m];

// with a loop
decimal total = 0m;
foreach (decimal kwh in usageKwh)
{
    if (kwh > 300m)
    {
        total += kwh;
    }
}
Console.WriteLine(total);

// with LINQ
Console.WriteLine(usageKwh.Where(kwh => kwh > 300m).Sum());

// numbered items, like Python's enumerate()
foreach (var (index, kwh) in usageKwh.Index())
{
    Console.WriteLine($"{index + 1}. {kwh} kWh");
}
1462.5
1462.5
1. 310.5 kWh
2. 512.0 kWh
3. 98.25 kWh
4. 640.0 kWh

The LINQ version says what you want rather than how to compute it, and it is hard to get an off-by-one error in it. A plain loop is the better choice when the body has side effects (printing, writing files, calling a service), when you need break, or when several results are built in one pass. Index() (added in .NET 9) pairs each item with its position, the C# counterpart of Python's enumerate(). (The trailing .0 in 512.0 is not a mistake: a decimal remembers how many decimals it was written with.)

Choosing a loop

Loop Use it when
foreach you want every item of a collection, in order (the most common case)
for you count, need the index, skip positions, change array items, or walk backwards
while the number of rounds depends on something that happens inside the loop
do … while the block must run at least once, as in input prompts
LINQ you filter, transform, sort or aggregate a collection