Section 5 of 10

Control Flow

🎯 What You'll Learn

  • How programs make decisions with if, else if, and else
  • Choosing between multiple options with switch statements
  • Modern pattern matching with switch expressions (C# 8+)
  • Repeating actions with for, while, do-while, and foreach loops
  • Controlling loop execution with break and continue
  • Nesting control structures for complex logic
  • Common patterns: input validation, menus, searching

What is Control Flow?

So far, our programs have been strictly linearβ€”each line executes once, in order, from top to bottom. But real programs need to be smarter. They need to:

  • Make decisions: "If the user is logged in, show their dashboard; otherwise, show the login page"
  • Repeat actions: "For each product in the cart, calculate the line total"
  • Skip or exit early: "If we found what we're looking for, stop searching"
πŸ’‘ Key Concept

Control flow refers to the order in which statements are executed in a program. Control flow statements let you change that orderβ€”branching to different code paths or repeating sections of code.

Think of control flow like driving directions. Linear code is "go straight for 10 miles." But real directions include decisions ("if the road is closed, take the detour") and repetition ("circle the roundabout until you see Exit 3"). Control flow gives your programs the same flexibility.

Conditional Statements: Making Decisions

Conditional statements execute different code based on whether a condition is true or false. The most fundamental is the if statement.

The if Statement

The if statement executes a block of code only when a condition is true.

IfStatement.cs C#
int temperature = 35;

if (temperature > 30)
{
    Console.WriteLine("It's hot outside!");
}

// The condition must be a bool (true/false)
bool isLoggedIn = true;

if (isLoggedIn)
{
    Console.WriteLine("Welcome back!");
}

// You can use any expression that evaluates to bool
string userRole = "admin";

if (userRole == "admin")
{
    Console.WriteLine("You have full access.");
}

The code inside the curly braces { } is called a block. It only runs if the condition is true. If false, the entire block is skipped.

The if-else Statement

Often you want to do one thing if a condition is true, and something else if it's false. That's where else comes in.

IfElseStatement.cs C#
int age = 16;

if (age >= 18)
{
    Console.WriteLine("You can vote.");
}
else
{
    Console.WriteLine("You're too young to vote.");
}

// Real-world example: checking stock
int quantityOnHand = 5;
int quantityOrdered = 10;

if (quantityOnHand >= quantityOrdered)
{
    Console.WriteLine("Order can be fulfilled.");
    quantityOnHand -= quantityOrdered;
}
else
{
    Console.WriteLine($"Insufficient stock. Only {quantityOnHand} available.");
}

The if-else if-else Chain

When you have multiple conditions to check, use else if to chain them together.

ElseIfChain.cs C#
int score = 85;
string grade;

if (score >= 90)
{
    grade = "A";
}
else if (score >= 80)
{
    grade = "B";
}
else if (score >= 70)
{
    grade = "C";
}
else if (score >= 60)
{
    grade = "D";
}
else
{
    grade = "F";
}

Console.WriteLine($"Your grade: {grade}");  // "Your grade: B"
ℹ️ How else-if Works

Conditions are checked from top to bottom. As soon as one is true, its block executes and all remaining conditions are skipped. Order matters! Put more specific conditions before more general ones.

Nested if Statements

You can place if statements inside other if statements for more complex logic.

NestedIf.cs C#
bool isLoggedIn = true;
string userRole = "editor";
bool hasPermission = true;

if (isLoggedIn)
{
    if (userRole == "admin")
    {
        Console.WriteLine("Full access granted.");
    }
    else if (userRole == "editor" && hasPermission)
    {
        Console.WriteLine("Editor access granted.");
    }
    else
    {
        Console.WriteLine("Read-only access.");
    }
}
else
{
    Console.WriteLine("Please log in first.");
}
πŸ’‘ Avoid Deep Nesting

Deeply nested code (3+ levels) becomes hard to read. Consider combining conditions with && or ||, using early returns, or refactoring into separate methods (we'll cover this in the Methods section).

Single-Line if (No Braces)

For simple, single-statement blocks, you can omit the curly braces:

SingleLineIf.cs C#
int x = 10;

// Without braces (single statement only)
if (x > 5)
    Console.WriteLine("x is greater than 5");

// With braces (always works, recommended)
if (x > 5)
{
    Console.WriteLine("x is greater than 5");
}
⚠️ Always Use Braces

Many developers (and style guides) recommend always using braces, even for single statements. It prevents bugs when you later add more statements and forget to add braces. This is a common source of hard-to-find bugs.

The switch Statement

When you're comparing a single value against many possible options, a switch statement is often cleaner than a long if-else chain.

Basic switch Syntax

SwitchStatement.cs C#
int dayOfWeek = 3;
string dayName;

switch (dayOfWeek)
{
    case 1:
        dayName = "Monday";
        break;
    case 2:
        dayName = "Tuesday";
        break;
    case 3:
        dayName = "Wednesday";
        break;
    case 4:
        dayName = "Thursday";
        break;
    case 5:
        dayName = "Friday";
        break;
    case 6:
        dayName = "Saturday";
        break;
    case 7:
        dayName = "Sunday";
        break;
    default:
        dayName = "Invalid day";
        break;
}

Console.WriteLine(dayName);  // "Wednesday"

Key points about switch:

  • Each case must end with break (or return, throw, etc.)
  • The default case handles any value not matched by other cases
  • You can switch on: integers, strings, chars, enums, and some other types

Multiple Cases for Same Code

You can stack multiple case labels to run the same code:

SwitchMultipleCases.cs C#
int dayOfWeek = 6;
string dayType;

switch (dayOfWeek)
{
    case 1:
    case 2:
    case 3:
    case 4:
    case 5:
        dayType = "Weekday";
        break;
    case 6:
    case 7:
        dayType = "Weekend";
        break;
    default:
        dayType = "Invalid";
        break;
}

Console.WriteLine(dayType);  // "Weekend"

Switching on Strings

SwitchOnString.cs C#
string command = "start";

switch (command.ToLower())  // Convert to lowercase for case-insensitive matching
{
    case "start":
        Console.WriteLine("Starting the process...");
        break;
    case "stop":
        Console.WriteLine("Stopping the process...");
        break;
    case "restart":
        Console.WriteLine("Restarting...");
        break;
    case "status":
        Console.WriteLine("Checking status...");
        break;
    default:
        Console.WriteLine($"Unknown command: {command}");
        break;
}

Switch with Pattern Matching (C# 7+)

Modern C# allows powerful pattern matching in switch statements:

SwitchPatternMatching.cs C#
// Type pattern matching
object value = 42;

switch (value)
{
    case int n:
        Console.WriteLine($"It's an integer: {n}");
        break;
    case string s:
        Console.WriteLine($"It's a string: {s}");
        break;
    case null:
        Console.WriteLine("It's null");
        break;
    default:
        Console.WriteLine("Unknown type");
        break;
}

// Pattern matching with conditions (when clause)
int number = 15;

switch (number)
{
    case int n when n < 0:
        Console.WriteLine("Negative");
        break;
    case int n when n == 0:
        Console.WriteLine("Zero");
        break;
    case int n when n > 0 && n <= 10:
        Console.WriteLine("Small positive");
        break;
    case int n when n > 10:
        Console.WriteLine("Large positive");
        break;
}

Switch Expressions (C# 8+)

Switch expressions are a more concise way to write switches that return a value. This is modern C# at its best:

SwitchExpression.cs C#
int dayOfWeek = 3;

// Switch expression - concise and returns a value
string dayName = dayOfWeek switch
{
    1 => "Monday",
    2 => "Tuesday",
    3 => "Wednesday",
    4 => "Thursday",
    5 => "Friday",
    6 => "Saturday",
    7 => "Sunday",
    _ => "Invalid day"  // _ is the discard pattern (like default)
};

// With pattern matching
int score = 85;

string grade = score switch
{
    >= 90 => "A",
    >= 80 => "B",
    >= 70 => "C",
    >= 60 => "D",
    _ => "F"
};

// Multiple values with or pattern
string dayType = dayOfWeek switch
{
    1 or 2 or 3 or 4 or 5 => "Weekday",
    6 or 7 => "Weekend",
    _ => "Invalid"
};

// With range pattern
string ageGroup = age switch
{
    < 0 => "Invalid",
    < 13 => "Child",
    < 20 => "Teenager",
    < 65 => "Adult",
    _ => "Senior"
};
πŸ’‘ Prefer Switch Expressions

When you need to assign a value based on a condition, switch expressions are cleaner and more concise than traditional switch statements. They're also harder to mess up because the compiler ensures all cases return a value.

Loops: Repeating Actions

Loops let you execute a block of code multiple times. C# provides four types of loops, each suited for different scenarios.

The for Loop

The for loop is best when you know exactly how many times you want to repeat something.

ForLoop.cs C#
// Basic for loop: count from 0 to 4
for (int i = 0; i < 5; i++)
{
    Console.WriteLine($"Iteration {i}");
}
// Output: Iteration 0, Iteration 1, Iteration 2, Iteration 3, Iteration 4

// Anatomy of a for loop:
// for (initialization; condition; update)
//   initialization: runs once before the loop starts (int i = 0)
//   condition: checked before each iteration (i < 5)
//   update: runs after each iteration (i++)

// Count from 1 to 10
for (int i = 1; i <= 10; i++)
{
    Console.WriteLine(i);
}

// Count backwards
for (int i = 10; i >= 1; i--)
{
    Console.WriteLine(i);
}

// Count by twos
for (int i = 0; i <= 20; i += 2)
{
    Console.WriteLine(i);  // 0, 2, 4, 6, ..., 20
}

// Real example: calculate sum of 1 to 100
int sum = 0;
for (int i = 1; i <= 100; i++)
{
    sum += i;
}
Console.WriteLine($"Sum: {sum}");  // Sum: 5050

The while Loop

The while loop repeats as long as a condition is true. Use it when you don't know in advance how many iterations you'll need.

WhileLoop.cs C#
// Basic while loop
int count = 0;

while (count < 5)
{
    Console.WriteLine($"Count: {count}");
    count++;
}

// Real example: input validation
string input = "";

while (string.IsNullOrWhiteSpace(input))
{
    Console.Write("Enter your name: ");
    input = Console.ReadLine() ?? "";
}
Console.WriteLine($"Hello, {input}!");

// Example: keep halving until less than 1
double value = 100.0;
int iterations = 0;

while (value >= 1)
{
    value /= 2;
    iterations++;
}
Console.WriteLine($"Took {iterations} iterations");  // 7 iterations
⚠️ Infinite Loop Danger

A while loop runs forever if its condition never becomes false. Always ensure something inside the loop eventually makes the condition false. If your program seems to hang, you might have an infinite loop (press Ctrl+C to stop it).

The do-while Loop

The do-while loop is like while, but it checks the condition after each iteration. This guarantees at least one execution.

DoWhileLoop.cs C#
// do-while always executes at least once
int number = 10;

do
{
    Console.WriteLine($"Number: {number}");
    number--;
} while (number > 0);

// Perfect for menu systems!
int choice;

do
{
    Console.WriteLine("\n=== InvenTrack Menu ===");
    Console.WriteLine("1. View Products");
    Console.WriteLine("2. Add Product");
    Console.WriteLine("3. Update Stock");
    Console.WriteLine("4. Exit");
    Console.Write("Enter choice: ");
    
    if (int.TryParse(Console.ReadLine(), out choice))
    {
        switch (choice)
        {
            case 1:
                Console.WriteLine("Showing products...");
                break;
            case 2:
                Console.WriteLine("Adding product...");
                break;
            case 3:
                Console.WriteLine("Updating stock...");
                break;
            case 4:
                Console.WriteLine("Goodbye!");
                break;
            default:
                Console.WriteLine("Invalid choice.");
                break;
        }
    }
    else
    {
        Console.WriteLine("Please enter a number.");
        choice = 0;  // Reset to continue loop
    }
    
} while (choice != 4);

The foreach Loop

The foreach loop iterates over every element in a collection. It's the cleanest way to process arrays, lists, and other collections.

ForeachLoop.cs C#
// Iterating over an array
string[] fruits = { "Apple", "Banana", "Cherry", "Date" };

foreach (string fruit in fruits)
{
    Console.WriteLine(fruit);
}

// Iterating over a string (characters)
string word = "Hello";

foreach (char letter in word)
{
    Console.WriteLine(letter);  // H, e, l, l, o
}

// Real example: calculating cart total
decimal[] prices = { 19.99m, 29.99m, 9.99m, 49.99m };
decimal total = 0m;

foreach (decimal price in prices)
{
    total += price;
}

Console.WriteLine($"Total: {total:C}");  // Total: $109.96

// Using var for type inference
foreach (var price in prices)
{
    Console.WriteLine(price);
}
ℹ️ foreach is Read-Only

You cannot modify the collection while iterating with foreach. The loop variable (fruit, price) is read-only. If you need to modify elements, use a regular for loop with index access.

Choosing the Right Loop

Loop Type Best For Example Use Case
for Known number of iterations, need index Process items 0-99, access array by index
while Unknown iterations, condition-based Read until end of file, wait for user input
do-while Must run at least once Menu systems, input validation
foreach Process every item in a collection Sum prices, display all items, search

Loop Control: break and continue

Sometimes you need to interrupt a loop's normal flowβ€”exit early or skip to the next iteration. That's where break and continue come in.

break: Exit the Loop Immediately

BreakStatement.cs C#
// Search for a value - stop when found
int[] numbers = { 4, 8, 15, 16, 23, 42 };
int searchFor = 16;
bool found = false;

foreach (int number in numbers)
{
    if (number == searchFor)
    {
        found = true;
        break;  // Exit loop immediately
    }
    Console.WriteLine($"Checked {number}");
}

Console.WriteLine(found ? "Found!" : "Not found");
// Output: Checked 4, Checked 8, Checked 15, Found!

// Breaking out of a while loop
int attempts = 0;
const int MaxAttempts = 3;

while (true)  // Infinite loop - we'll break out manually
{
    Console.Write("Enter password: ");
    string password = Console.ReadLine() ?? "";
    
    if (password == "secret123")
    {
        Console.WriteLine("Access granted!");
        break;  // Correct password - exit loop
    }
    
    attempts++;
    
    if (attempts >= MaxAttempts)
    {
        Console.WriteLine("Too many attempts. Account locked.");
        break;  // Too many attempts - exit loop
    }
    
    Console.WriteLine($"Wrong password. {MaxAttempts - attempts} attempts remaining.");
}

continue: Skip to the Next Iteration

ContinueStatement.cs C#
// Skip negative numbers
int[] numbers = { 1, -2, 3, -4, 5, -6, 7 };
int sum = 0;

foreach (int number in numbers)
{
    if (number < 0)
    {
        continue;  // Skip this iteration, go to next number
    }
    sum += number;
}

Console.WriteLine($"Sum of positives: {sum}");  // 16

// Skip empty lines when processing
string[] lines = { "Hello", "", "World", "   ", "!" };

foreach (string line in lines)
{
    if (string.IsNullOrWhiteSpace(line))
    {
        continue;  // Skip blank lines
    }
    Console.WriteLine($"Processing: {line}");
}
// Output: Processing: Hello, Processing: World, Processing: !

// Real example: process only active products
foreach (var product in products)
{
    if (!product.IsActive)
    {
        continue;  // Skip inactive products
    }
    
    // Process active product...
    Console.WriteLine($"Processing {product.Name}");
}
πŸ’‘ break vs continue

break β€” Exits the loop completely. Code after the loop runs next.
continue β€” Skips the rest of the current iteration. The loop continues with the next iteration.

Nested Loops

You can place loops inside other loops. This is useful for working with multi-dimensional data like grids, tables, or matrices.

NestedLoops.cs C#
// Multiplication table
for (int row = 1; row <= 5; row++)
{
    for (int col = 1; col <= 5; col++)
    {
        Console.Write($"{row * col,4}");  // Right-align in 4 characters
    }
    Console.WriteLine();  // New line after each row
}

/*
Output:
   1   2   3   4   5
   2   4   6   8  10
   3   6   9  12  15
   4   8  12  16  20
   5  10  15  20  25
*/

// Breaking out of nested loops (using a flag)
bool found = false;

for (int i = 0; i < 10 && !found; i++)
{
    for (int j = 0; j < 10 && !found; j++)
    {
        if (grid[i, j] == target)
        {
            Console.WriteLine($"Found at [{i},{j}]");
            found = true;
        }
    }
}

Putting It All Together: InvenTrack Stock Checker

Let's apply everything we've learned to a practical scenarioβ€”checking inventory and making decisions about stock levels:

InvenTrackStockChecker.cs C#
// Product data (we'll use proper classes later)
string[] productNames = { "Widget A", "Widget B", "Gadget X", "Gadget Y", "Tool Z" };
int[] quantities = { 150, 8, 0, 45, 12 };
int[] reorderLevels = { 25, 10, 5, 20, 15 };
decimal[] prices = { 19.99m, 29.99m, 49.99m, 39.99m, 89.99m };
bool[] isActive = { true, true, false, true, true };

// Counters for summary
int outOfStockCount = 0;
int lowStockCount = 0;
int healthyStockCount = 0;
decimal totalInventoryValue = 0m;

Console.WriteLine("╔══════════════════════════════════════════════════════════════╗");
Console.WriteLine("β•‘            INVENTRAK STOCK STATUS REPORT                     β•‘");
Console.WriteLine("β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•");
Console.WriteLine();

// Process each product
for (int i = 0; i < productNames.Length; i++)
{
    // Skip inactive products
    if (!isActive[i])
    {
        Console.WriteLine($"[INACTIVE] {productNames[i]} - Skipped");
        continue;
    }
    
    // Determine stock status
    string status;
    string action;
    
    if (quantities[i] == 0)
    {
        status = "πŸ”΄ OUT OF STOCK";
        action = "URGENT: Order immediately!";
        outOfStockCount++;
    }
    else if (quantities[i] <= reorderLevels[i])
    {
        status = "🟑 LOW STOCK";
        action = "Reorder soon";
        lowStockCount++;
    }
    else
    {
        status = "🟒 IN STOCK";
        action = "No action needed";
        healthyStockCount++;
    }
    
    // Calculate inventory value
    decimal productValue = quantities[i] * prices[i];
    totalInventoryValue += productValue;
    
    // Display product info
    Console.WriteLine($"Product: {productNames[i]}");
    Console.WriteLine($"  Quantity: {quantities[i]} (Reorder at: {reorderLevels[i]})");
    Console.WriteLine($"  Value: {productValue:C}");
    Console.WriteLine($"  Status: {status}");
    Console.WriteLine($"  Action: {action}");
    Console.WriteLine();
}

// Summary using switch expression
string overallStatus = outOfStockCount switch
{
    > 2 => "CRITICAL - Multiple products out of stock!",
    > 0 => "WARNING - Some products need attention",
    _ when lowStockCount > 0 => "CAUTION - Low stock items present",
    _ => "HEALTHY - All products well stocked"
};

Console.WriteLine("════════════════════════════════════════════════════════════════");
Console.WriteLine("SUMMARY");
Console.WriteLine("════════════════════════════════════════════════════════════════");
Console.WriteLine($"  🟒 Healthy Stock: {healthyStockCount} products");
Console.WriteLine($"  🟑 Low Stock:     {lowStockCount} products");
Console.WriteLine($"  πŸ”΄ Out of Stock:  {outOfStockCount} products");
Console.WriteLine($"  πŸ’° Total Value:   {totalInventoryValue:C}");
Console.WriteLine();
Console.WriteLine($"  Overall: {overallStatus}");

Key Takeaways

  • if/else if/else executes code based on boolean conditions
  • switch statements are cleaner than long if-else chains when comparing one value to many options
  • Switch expressions (C# 8+) are concise when you need to return a value based on a condition
  • for loops are best when you know how many iterations you need
  • while loops repeat as long as a condition is true (check first)
  • do-while loops guarantee at least one execution (check after)
  • foreach loops iterate over every item in a collection
  • break exits a loop immediately; continue skips to the next iteration
  • Always use braces { } even for single-statement blocks
  • Avoid deep nestingβ€”consider combining conditions or refactoring
πŸš€ Ready for Methods!

You can now make decisions and repeat actions. In the next section, we'll learn how to organize code into reusable methodsβ€”the building blocks of well-structured programs.

What's Next?

In the next section, Methods and Functions, we'll learn how to break our code into reusable pieces. You'll understand parameters, return values, method overloading, and how methods make code more organized and maintainable.