C# Loops — for, foreach, while, do-while, break, continue, and yield return
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
- Write a
forloop that prints all even numbers from 0 to 20. - How does
foreachwork with arrays internally? - What is the difference between
breakandcontinue? - Write a method using
yield returnthat generates all prime numbers up to a given limit. - When would you use a
do-whileloop instead of awhileloop?
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
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