Skip to content

C# Loops — for, foreach, while, do-while, break, continue, and yield return

DodaTech Updated 2026-06-28 8 min read

In this tutorial, you will learn about C# Loops. We cover key concepts, practical examples, and best practices to help you master this topic.

C# provides four loop constructs for iteration — for, foreach, while, and do-while — combined with break, continue, and yield return for fine-grained control over loop execution and sequence generation.

What You'll Learn

You will master every loop in C#: the for loop for index-based iteration, foreach for enumerating collections, while for condition-controlled loops, and do-while for guaranteed-first-execution. You will also learn break and continue for loop control, and yield return for creating lazy sequences.

Why It Matters

Loops are fundamental to processing collections, generating sequences, and implementing algorithms. Choosing the right loop affects code readability, performance, and correctness. The foreach loop works with any .NET type implementing IEnumerable<T>, making it the most common loop in everyday C# code. Understanding yield return is essential for building efficient Data Pipelines.

Real-World Use

Web applications use foreach to iterate over database results. Data processing pipelines use yield return to stream large datasets without loading everything into memory. Game loops use while for the main game update cycle. Background services use for loops with cancellation tokens for periodic maintenance tasks.

Learning Path

graph LR
    A["07: Control Flow"] --> B["08: Loops"]
    B --> C["09: Classes"]
    C --> D["10: Constructors"]
    D --> E["11: Encapsulation"]
    style A fill:#4a90d9,stroke:#2c5f8a,color:#fff
    style B fill:#4a90d9,stroke:#2c5f8a,color:#fff
    style C fill:#4a90d9,stroke:#2c5f8a,color:#fff
    style D fill:#4a90d9,stroke:#2c5f8a,color:#fff
    style E fill:#4a90d9,stroke:#2c5f8a,color:#fff

The for Loop

// Basic for loop: initialize; condition; increment
for (int i = 0; i < 5; i++)
{
    Console.WriteLine($"Iteration {i}");
}

Expected output:

Iteration 0
Iteration 1
Iteration 2
Iteration 3
Iteration 4

For loop variations

// Multiple variables
for (int i = 0, j = 10; i < j; i++, j--)
{
    Console.WriteLine($"i={i}, j={j}");
}

// Array iteration with index
int[] numbers = { 10, 20, 30, 40, 50 };
for (int i = 0; i < numbers.Length; i++)
{
    numbers[i] *= 2;  // Modify in place
}
Console.WriteLine(string.Join(", ", numbers));  // 20, 40, 60, 80, 100

// Infinite loop with break
for (;;)
{
    var key = Console.ReadKey();
    if (key.Key == ConsoleKey.Escape) break;
}

// Descending loop
for (int i = 10; i >= 0; i--)
{
    Console.WriteLine($"Countdown: {i}");
}

The foreach Loop

foreach iterates over any type that implements IEnumerable<T>:

// Array
int[] numbers = { 1, 2, 3, 4, 5 };
foreach (int num in numbers)
{
    Console.Write($"{num} ");
}
Console.WriteLine();  // 1 2 3 4 5

// List
List<string> fruits = new() { "Apple", "Banana", "Cherry" };
foreach (string fruit in fruits)
{
    Console.WriteLine(fruit);
}

// String (characters)
foreach (char c in "Hello")
{
    Console.WriteLine($"'{c}' = {(int)c}");
}

// Dictionary
Dictionary<string, int> scores = new()
{
    {"Alice", 95},
    {"Bob", 87},
    {"Charlie", 92}
};
foreach (KeyValuePair<string, int> pair in scores)
{
    Console.WriteLine($"{pair.Key}: {pair.Value}");
}

// Using var with deconstruction
foreach (var (name, score) in scores)
{
    Console.WriteLine($"{name}: {score}");
}

The foreach limitation

You cannot modify the loop variable inside foreach. Use a for loop instead:

// This does NOT work:
foreach (int num in numbers)
{
    num = num * 2;  // Compilation error!
}

// Use for loop:
for (int i = 0; i < numbers.Length; i++)
{
    numbers[i] = numbers[i] * 2;
}

The while Loop

// Basic while
int count = 0;
while (count < 5)
{
    Console.WriteLine($"Count: {count}");
    count++;
}

// Password guessing game
string password = "secret";
string guess = "";
int attempts = 0;

while (guess != password && attempts < 3)
{
    Console.Write("Enter password: ");
    guess = Console.ReadLine();
    attempts++;
}

if (guess == password)
{
    Console.WriteLine("Access granted");
}
else
{
    Console.WriteLine("Access denied");
}

// Reading until empty input
string? line;
while ((line = Console.ReadLine()) != null && line != "")
{
    Console.WriteLine($"Echo: {line}");
}

The do-while Loop

Guarantees the body executes at least once:

// Menu system
string choice;
do
{
    Console.WriteLine("\nMenu:");
    Console.WriteLine("1. View items");
    Console.WriteLine("2. Add item");
    Console.WriteLine("3. Exit");
    Console.Write("Choice: ");
    choice = Console.ReadLine();

    switch (choice)
    {
        case "1": Console.WriteLine("Displaying items..."); break;
        case "2": Console.WriteLine("Adding item..."); break;
        case "3": Console.WriteLine("Goodbye!"); break;
        default: Console.WriteLine("Invalid choice"); break;
    }
} while (choice != "3");

Break and Continue

// break: exit the loop immediately
for (int i = 0; i < 10; i++)
{
    if (i == 5) break;  // Loop stops at 5
    Console.WriteLine(i);
}
// Output: 0 1 2 3 4

// continue: skip to next iteration
for (int i = 0; i < 10; i++)
{
    if (i % 2 == 0) continue;  // Skip even numbers
    Console.WriteLine(i);
}
// Output: 1 3 5 7 9

// break with nested loops (needs only inner)
for (int row = 0; row < 3; row++)
{
    for (int col = 0; col < 3; col++)
    {
        if (row == 1 && col == 1) break;  // Breaks inner loop only
        Console.Write($"({row},{col}) ");
    }
    Console.WriteLine();
}

Yield Return

yield return creates lazy sequences that generate values on-demand:

IEnumerable<int> GetFibonacci(int count)
{
    int a = 0, b = 1;
    for (int i = 0; i < count; i++)
    {
        yield return a;
        (a, b) = (b, a + b);
    }
}

// Lazy evaluation (no computation happens here)
var fib = GetFibonacci(10);

// Computation happens during iteration
foreach (int num in fib)
{
    Console.Write($"{num} ");
}
// Output: 0 1 1 2 3 5 8 13 21 34

Lazy evaluation benefits

IEnumerable<int> GetNumbers()
{
    for (int i = 0; i < 1000; i++)
    {
        if (i > 1000) yield break;  // Stop generating
        yield return i;
    }
}

// Only generates values until First() finds a match
int firstEven = GetNumbers()
    .Where(n => n > 100)
    .First(n => n % 2 == 0);

Console.WriteLine(firstEven);  // 102

Loop Performance Considerations

// Inefficient: count evaluated each iteration
for (int i = 0; i < list.Count; i++)  // Count property called each time
{
    // ...
}

// Better: cache the count
int count = list.Count;
for (int i = 0; i < count; i++)
{
    // ...
}

// foreach with List<T> is optimized by the compiler
// into a for loop internally

Common Mistakes

Mistake 1: Off-by-One Errors

for (int i = 0; i <= array.Length; i++) causes an index-out-of-range exception. Use < not <=.

Mistake 2: Modifying Collections During foreach

Adding or removing items from a collection inside a foreach loop throws InvalidOperationException. Use a for loop or collect items to remove in a separate list.

Mistake 3: Infinite Loops

// Infinite: missing increment
for (int i = 0; i < 10; ) { }

// Infinite: wrong increment direction
for (int i = 0; i < 10; i--) { }

Mistake 4: Assuming foreach Creates a Copy

foreach does not copy the collection. It uses the enumerator directly. Do not modify the collection during iteration.

Mistake 5: Forgetting yield break in Iterators

Without yield break, an Iterator method continues until the end. Use yield break to stop iteration early conditionally.

Mistake 6: Using for When foreach Is Clearer

For simple enumeration where you do not need the index, foreach is cleaner and less error-prone.

Practice Questions

  1. Write a for loop that prints all even numbers from 0 to 20.
  2. How does foreach work with arrays internally?
  3. What is the difference between break and continue?
  4. Write a method using yield return that generates all prime numbers up to a given limit.
  5. When would you use a do-while loop instead of a while loop?

Challenge

Create an iterator method GetPowersOfTwo(int max) that yields powers of 2 up to max using yield return. Then use LINQ to find the first power of 2 that is greater than 1000. Demonstrate lazy evaluation by adding a Console.WriteLine in the iterator.

FAQ

Can I use `await` inside a foreach loop?

Yes. You can use await foreach with IAsyncEnumerable<T> for asynchronous iteration. Each iteration awaits the next item asynchronously.

What is the difference between `for` and `foreach` performance?

For arrays, both are equivalent. For List, the compiler optimizes foreach into a for loop. For other collections, foreach uses the enumerator which may be slightly slower but more readable.

Can I change the loop variable in a foreach?

No. The loop variable is read-only in foreach. Use a for loop if you need to modify the collection element or track the index.

How does `yield return` work internally?

The compiler generates a state machine class that implements IEnumerable and IEnumerator. Each yield return creates a state that stores the current position and local variables.

What happens if I iterate a `yield` method twice?

Each iteration creates a new enumerator instance. The method executes from the beginning each time. The sequence is not cached unless you materialize it with .ToList() or .ToArray().

Mini Project

Create a prime number generator and analyzer:

IEnumerable<int> GetPrimes()
{
    yield return 2;
    int candidate = 3;
    while (true)
    {
        bool isPrime = true;
        for (int divisor = 3; divisor * divisor <= candidate; divisor += 2)
        {
            if (candidate % divisor == 0)
            {
                isPrime = false;
                break;
            }
        }
        if (isPrime) yield return candidate;
        candidate += 2;
    }
}

Console.WriteLine("First 20 prime numbers:");
int count = 0;
var primes = GetPrimes().GetEnumerator();
while (count < 20 && primes.MoveNext())
{
    Console.Write($"{primes.Current} ");
    count++;
}
Console.WriteLine();

// Find prime numbers in a range
Console.WriteLine("\nPrime numbers between 50 and 100:");
foreach (int prime in GetPrimes())
{
    if (prime > 100) break;
    if (prime >= 50) Console.Write($"{prime} ");
}
Console.WriteLine();

Expected output:

First 20 prime numbers:
2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71

Prime numbers between 50 and 100:
53 59 61 67 71 73 79 83 89 97

What's Next

You have mastered all loop constructs in C# including lazy iteration with yield return. The next lesson covers classes: class syntax, fields, properties, auto-properties, and init-only setters.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro