Skip to main content
ANVISoftware Solutions
Lesson 15 of 62Beginner15 min

Loops in C#

By the end of this lesson

Use for, foreach, while and do-while, and control flow with break and continue.

C# has four loop forms. Choosing between them comes down to two questions: do you need to know the position of each item, and do you know in advance how many times to repeat?

What each form is for:

for
You know where to start, when to stop and how to move on. Use it when the position matters, or when you are counting rather than walking a collection.
foreach
Walks every item in a collection in order. Use it when you care about the items and not their positions.
while
Repeats while a condition holds, checked before each pass. The body may never run at all.
do-while
The same, with the condition checked after each pass, so the body always runs at least once.
foreach for the items, for when the position matters
C#
List<decimal> lineTotals = new List<decimal> { 24.99m, 150m, 8.50m };

decimal orderTotal = 0m;
foreach (decimal lineTotal in lineTotals)
{
    orderTotal += lineTotal;
}

for (int i = 0; i < lineTotals.Count; i++)
{
    Console.WriteLine($"Line {i + 1}: {lineTotals[i]:N2}");
}

Console.WriteLine($"Order total: {orderTotal:N2}");
  • foreach hands you each value and nothing else. There is no position, and you cannot assign to lineTotal — the loop variable is read-only.
  • The for loop's three parts are: where to start, the condition to keep going, and what to do after each pass.
  • It is i < Count, not i <= Count. The last valid position is Count - 1, so <= asks for an item that does not exist and throws ArgumentOutOfRangeException on the final pass.
  • The output uses i + 1 because people count from one while positions start at zero.
while when the count is not known, do-while when you must ask once
C#
int unitsToPack = 7;
int unitsPerBox = 3;
int boxesUsed = 0;

while (unitsToPack > 0)
{
    int packed = Math.Min(unitsPerBox, unitsToPack);
    unitsToPack -= packed;
    boxesUsed++;
}

Console.WriteLine($"Boxes needed: {boxesUsed}");   // 3

string? command;

do
{
    Console.Write("Command (type quit to stop): ");
    command = Console.ReadLine();
    Console.WriteLine($"Received: {command}");
}
while (!string.Equals(command, "quit", StringComparison.OrdinalIgnoreCase));
  • while checks the condition first, so a loop whose condition starts out false never runs its body once.
  • do-while checks afterwards, which suits a prompt: you have to ask before you can know whether to stop.
  • Every while loop needs something inside it that moves towards the condition becoming false. Here unitsToPack shrinks each pass. Remove that line and the program does not crash — it hangs, which is harder to spot.
  • command is declared before the loop because the condition below refers to it. A variable declared inside the braces would not be visible there.

break and continue

C#
List<string> skus = new List<string> { "LAMP-01", "", "STAND-04", "TIDY-09" };

foreach (string sku in skus)
{
    if (string.IsNullOrWhiteSpace(sku))
    {
        continue;    // skip this one, carry on with the next
    }

    Console.WriteLine($"Checked {sku}");

    if (sku == "STAND-04")
    {
        Console.WriteLine("Found the one we wanted.");
        break;       // stop the loop completely
    }
}
  • continue abandons the current pass and starts the next one. It is a tidy way to filter without wrapping the whole body in an if.
  • break leaves the loop: nothing after it in the body runs, and no further items are visited.
  • In nested loops, both affect the innermost loop only. Getting out of two levels at once usually means pulling the inner loop into its own method and returning from it.

Summary

  • foreach for items, for when the position matters, while when the count is unknown, do-while when the body must run once
  • The last position in a list is Count - 1, which is why the condition is < and not <=
  • continue skips the current pass; break ends the loop, and both apply to the innermost loop only
  • Modifying a collection during a foreach throws — remove backwards with for, or apply changes afterwards
  • A while loop needs something in its body that moves the condition towards false

Practice

Attempt each one before opening the solution. Getting it wrong first is how the idea sticks.

Try it yourself

Try it yourself

Print a numbered picking list from a list of SKUs. Skip any blank entry, and stop after ten printed lines so a warehouse worker gets one page.

Think about whether the line number should count all items or only the ones printed.

Show solution

A separate counter for printed lines is the point of this exercise. Using the loop position as the line number leaves gaps wherever a blank was skipped, which looks like missing stock to the person holding the sheet.

The counter also drives the stopping condition, so the two rules are expressed with the same variable rather than being kept in step by hand.

C#
List<string> skus = new List<string>
{
    "LAMP-01", "", "STAND-04", "TIDY-09", "CHAIR-02", "",
    "DESK-07", "MAT-03", "HOOK-05", "TRAY-06", "PEN-08", "CLIP-10", "BIN-11",
};

int printed = 0;

foreach (string sku in skus)
{
    if (string.IsNullOrWhiteSpace(sku))
    {
        continue;
    }

    printed++;
    Console.WriteLine($"{printed}. {sku}");

    if (printed == 10)
    {
        Console.WriteLine("End of page 1.");
        break;
    }
}

Challenge

Challenge

Remove every line with a zero quantity from a List of order lines, editing the list in place rather than building a new one.

Try it first with a normal forward for loop and watch what happens with two zero-quantity lines next to each other.

Show solution

Removing while moving forwards shifts every later item down one position, so the next value of i skips the item that moved into the gap. Two adjacent zeros means the second survives.

Running backwards fixes it, because a removal only shifts positions you have already visited.

RemoveAll does the same job in one line and is what most code uses. The loop is worth writing once, because the shifting-index problem shows up any time you modify a collection you are walking — and it does not announce itself with an exception.

C#
List<(string Sku, int Quantity)> lines = new List<(string, int)>
{
    ("LAMP-01", 2),
    ("STAND-04", 0),
    ("TIDY-09", 0),
    ("CHAIR-02", 1),
};

for (int i = lines.Count - 1; i >= 0; i--)
{
    if (lines[i].Quantity == 0)
    {
        lines.RemoveAt(i);
    }
}

Console.WriteLine(string.Join(", ", lines.Select(line => line.Sku)));   // LAMP-01, CHAIR-02

// One-line equivalent:
// lines.RemoveAll(line => line.Quantity == 0);

Knowledge check

Nothing is recorded and there is no score. The explanation appears either way.

A foreach loop over a List<string> adds an item to that same list. What happens?

Saved in this browser only.