Chapter 4 of 23

Control Flow — Conditions and Loops

Master if/else, switch-case, all loop types, break/continue, and build real programs — a multiplication table and the Sieve of Eratosthenes.

Meritshot13 min read
C++Control Flowif elseLoopsSwitchSieve of Eratosthenes
All C++ Chapters

Control Flow — Conditions and Loops

Every interesting program makes decisions and repeats tasks. Without these capabilities, a program would execute each line exactly once, top to bottom, and terminate — not very useful. Control flow is the umbrella term for the language constructs that alter this default sequential execution.

In C++ there are two main categories:

  • Conditional statements — execute a block of code only when a condition is true.
  • Loops — repeat a block of code while a condition holds.

Mastering these is non-negotiable: virtually every algorithm you will write in competitive programming, every data-processing pipeline, and every game logic system lives inside loops and conditionals.


Conditional Statements

if, else if, else

The fundamental conditional in C++:

if (condition1) {
    // executed when condition1 is true
} else if (condition2) {
    // executed when condition1 is false AND condition2 is true
} else {
    // executed when all above conditions are false
}

A concrete example — classifying a CGPA for campus placements:

#include <iostream>

int main() {
    double cgpa;
    std::cout << "Enter your CGPA: ";
    std::cin >> cgpa;

    if (cgpa >= 9.0) {
        std::cout << "Eligible for Dream Companies (Google, Microsoft, Amazon)\n";
    } else if (cgpa >= 7.5) {
        std::cout << "Eligible for Mass Recruiters (TCS, Infosys, Wipro)\n";
    } else if (cgpa >= 6.0) {
        std::cout << "Eligible for Off-Campus Applications\n";
    } else {
        std::cout << "Focus on improving CGPA before applying\n";
    }

    return 0;
}

Important: The condition inside if (...) must evaluate to bool. In C++, any non-zero integer is treated as true and zero as false. This is legal but confusing — write explicit comparisons (x != 0) rather than relying on implicit conversion.

Single-Statement Bodies

When an if or else body contains exactly one statement, the braces are technically optional:

if (x > 0)
    std::cout << "Positive\n";

However, always use braces. Without them, adding a second statement creates a bug that is notoriously hard to spot (the famous Apple SSL "goto fail" vulnerability was caused by exactly this pattern). Treat braces as mandatory.

The Ternary Operator

The ternary (conditional) operator is a compact single-expression form of if/else:

result = (condition) ? value_if_true : value_if_false;

Example — finding the maximum of two numbers:

int a = 42, b = 17;
int maxVal = (a > b) ? a : b;
std::cout << "Max: " << maxVal << "\n";   // Max: 42

Use the ternary operator for simple, readable two-branch expressions. Avoid nesting ternary operators (a ? b : c ? d : e) — it becomes unreadable quickly.


The switch Statement

When you need to compare a single integer (or character) against many fixed values, switch is cleaner and often faster than a long if-else if chain:

switch (expression) {
    case value1:
        // code
        break;
    case value2:
        // code
        break;
    default:
        // code when no case matches
        break;
}

Example — mapping a menu selection in a college management system:

#include <iostream>

int main() {
    int choice;
    std::cout << "1. View Marks\n2. View Attendance\n3. View Fee Status\nEnter choice: ";
    std::cin >> choice;

    switch (choice) {
        case 1:
            std::cout << "Fetching your marks...\n";
            break;
        case 2:
            std::cout << "Fetching attendance records...\n";
            break;
        case 3:
            std::cout << "Fetching fee status...\n";
            break;
        default:
            std::cout << "Invalid option. Please enter 1, 2, or 3.\n";
            break;
    }

    return 0;
}

Fall-Through

If you omit break, execution falls through to the next case. This is occasionally intentional:

char grade = 'B';

switch (grade) {
    case 'A':
    case 'B':
    case 'C':
        std::cout << "Passed\n";   // all three cases reach this
        break;
    case 'F':
        std::cout << "Failed\n";
        break;
    default:
        std::cout << "Invalid grade\n";
        break;
}

Here, 'A', 'B', and 'C' all fall through to the "Passed" output — a deliberate and common pattern for grouping cases. When fall-through is unintentional, it is a bug. Some compilers warn about implicit fall-through; in C++17 you can annotate intentional fall-throughs with [[fallthrough]] to silence the warning and document intent.

Limitations of switch: It only works with integral types (int, char, enum) and requires constant case values known at compile time. You cannot use switch with std::string or with runtime-computed ranges.


Loops

The while Loop

Repeats a block as long as the condition remains true. The condition is checked before each iteration.

while (condition) {
    // body
}

Example — computing the sum of digits of a number:

#include <iostream>

int main() {
    int n;
    std::cout << "Enter a number: ";
    std::cin >> n;

    int digitSum = 0;
    while (n > 0) {
        digitSum += n % 10;  // add last digit
        n /= 10;             // remove last digit
    }

    std::cout << "Sum of digits: " << digitSum << "\n";
    return 0;
}

If the condition is false from the start, the body never executes.

The do-while Loop

Similar to while, but the condition is checked after the first iteration — guaranteeing the body runs at least once:

do {
    // body
} while (condition);

Example — input validation (keep asking until the user enters a valid CGPA):

#include <iostream>

int main() {
    double cgpa;
    do {
        std::cout << "Enter CGPA (0.0 to 10.0): ";
        std::cin >> cgpa;
        if (cgpa < 0.0 || cgpa > 10.0) {
            std::cout << "Invalid CGPA. Please try again.\n";
        }
    } while (cgpa < 0.0 || cgpa > 10.0);

    std::cout << "Valid CGPA entered: " << cgpa << "\n";
    return 0;
}

Use do-while when the loop body must execute at least once before the exit condition makes sense — most commonly for input validation menus.

The for Loop

The most commonly used loop in C++, ideal when the number of iterations is known:

for (initialisation; condition; update) {
    // body
}

Execution order: initialisation → (condition → body → update) → (condition → body → update) → ...

Example — printing the first 10 natural numbers:

#include <iostream>

int main() {
    for (int i = 1; i <= 10; i++) {
        std::cout << i << " ";
    }
    std::cout << "\n";
    return 0;
}

All three parts of a for loop are optional:

  • for (;;) is an infinite loop (equivalent to while (true)).
  • You can declare multiple variables in the initialisation: for (int i = 0, j = 10; i < j; i++, j--).

Range-Based for Loop (C++11)

The modern way to iterate over a collection without managing indices:

for (type element : collection) {
    // use element
}

Example — computing the average mark from an array:

#include <iostream>

int main() {
    int marks[] = {85, 92, 78, 66, 90};
    int total = 0;
    int count = 0;

    for (int mark : marks) {
        total += mark;
        count++;
    }

    double average = static_cast<double>(total) / count;
    std::cout << "Average: " << average << "\n";   // Average: 82.2
    return 0;
}

To modify elements during iteration, use a reference:

for (int& mark : marks) {
    mark += 5;   // add 5 grace marks to each subject
}

break, continue, and goto

break

Immediately exits the innermost loop (or switch) and transfers control to the statement after it:

for (int i = 1; i <= 100; i++) {
    if (i * i > 500) {
        std::cout << "Largest i where i*i <= 500: " << (i - 1) << "\n";
        break;
    }
}

continue

Skips the rest of the current iteration and jumps directly to the loop's update expression (for for loops) or re-evaluates the condition (for while/do-while):

// Print only even numbers from 1 to 20
for (int i = 1; i <= 20; i++) {
    if (i % 2 != 0) continue;  // skip odd numbers
    std::cout << i << " ";
}
// Output: 2 4 6 8 10 12 14 16 18 20

goto — and Why to Avoid It

goto transfers execution unconditionally to a labelled statement:

int i = 0;
loop_start:
    std::cout << i << " ";
    i++;
    if (i < 5) goto loop_start;

goto is legal C++ but universally discouraged in modern code because it creates spaghetti control flow — code whose execution path is impossible to follow without tracing every label. Dijkstra's famous 1968 paper "Go To Statement Considered Harmful" laid out the arguments formally, and they still hold.

The only marginal exception is breaking out of deeply nested loops, where goto avoids a complex chain of break/flag variables — but even then, refactoring into a function with return is the preferred alternative.


Nested Loops

Loops can be nested inside each other. Each iteration of the outer loop triggers a complete run of the inner loop.

Worked Example 1: Multiplication Table

#include <iostream>
#include <iomanip>   // for std::setw

int main() {
    const int N = 10;

    // Print header row
    std::cout << "   ";
    for (int j = 1; j <= N; j++) {
        std::cout << std::setw(5) << j;
    }
    std::cout << "\n";
    std::cout << std::string(53, '-') << "\n";

    // Print table body
    for (int i = 1; i <= N; i++) {
        std::cout << std::setw(2) << i << " |";
        for (int j = 1; j <= N; j++) {
            std::cout << std::setw(5) << i * j;
        }
        std::cout << "\n";
    }

    return 0;
}

Output (partial):

      1    2    3    4    5    6    7    8    9   10
-----------------------------------------------------
 1 |   1    2    3    4    5    6    7    8    9   10
 2 |   2    4    6    8   10   12   14   16   18   20
 3 |   3    6    9   12   15   18   21   24   27   30
...

Worked Example 2: Sieve of Eratosthenes

The Sieve of Eratosthenes is one of the oldest and most elegant algorithms — it finds all prime numbers up to N in O(N log log N) time. It appears in virtually every competitive programming contest involving primes. Here is how it works:

  1. Start with an array of true (assume all numbers are prime).
  2. For every number p starting from 2: if p is still marked prime, mark all its multiples (starting from p*p) as non-prime.
  3. After processing, every index still marked true is a prime.
#include <iostream>
#include <vector>

int main() {
    const int N = 100;

    // isPrime[i] = true means i is prime
    std::vector<bool> isPrime(N + 1, true);
    isPrime[0] = false;
    isPrime[1] = false;

    for (int p = 2; p * p <= N; p++) {
        if (isPrime[p]) {
            // Mark all multiples of p starting from p*p
            for (int multiple = p * p; multiple <= N; multiple += p) {
                isPrime[multiple] = false;
            }
        }
    }

    std::cout << "Primes up to " << N << ":\n";
    int count = 0;
    for (int i = 2; i <= N; i++) {
        if (isPrime[i]) {
            std::cout << i << " ";
            count++;
        }
    }
    std::cout << "\n\nTotal primes: " << count << "\n";

    return 0;
}

Output:

Primes up to 100:
2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97

Total primes: 25

Why start inner loop at p * p? All multiples of p smaller than p * p (i.e., 2p, 3p, ...) were already marked by smaller primes. Starting at p * p avoids redundant work and saves roughly half the iterations.

This sieve is a must-know for competitive programmers. At companies like Google India and Flipkart, prime-related problems appear often enough that having the sieve memorised is a real competitive advantage.


Common Pitfalls

Off-by-one errors. The most common loop bug. for (int i = 0; i < n; i++) iterates n times (indices 0 to n-1); for (int i = 0; i <= n; i++) iterates n+1 times. Think carefully about whether your bounds should use < or <=.

Infinite loops. A while (true) loop with a missing or unreachable break will run forever and hang your program. Always trace the exit condition before running.

Modifying the loop variable inside a for loop. If you write i++ inside the body of a for (int i = 0; i < n; i++) loop, you are incrementing i twice per iteration. This is usually a bug. Modify the loop variable only in the update expression.

switch without break. Forgetting break in a switch case causes fall-through to the next case, which is almost always unintended. Cultivate the habit of adding break immediately after writing each case body.

Comparing float in loop conditions. for (float f = 0.0f; f != 1.0f; f += 0.1f) may never terminate because 0.1f cannot be represented exactly in binary floating-point and the cumulative rounding error means f may skip over 1.0f. Use integer loop counters and compute floating-point values inside the body.

goto across variable initialisations. Jumping into a block with goto that bypasses a variable initialisation is undefined behaviour. This is another reason to avoid goto entirely.


Practice Exercises

  1. Write a program that prints all numbers from 1 to 100. For multiples of 3, print "Fizz" instead; for multiples of 5, print "Buzz"; for multiples of both 3 and 5, print "FizzBuzz". (This is a classic FAANG screening question.)

  2. Read a positive integer n and print an inverted triangle of stars:

*****
****
***
**
*
  1. Extend the Sieve of Eratosthenes to N = 10^6. Count the number of primes up to 10^6 (the answer is 78,498). Measure how fast your program runs.

  2. Write a program that reads an integer and prints the multiplication table for that number from 1 to 20 (e.g., the table of 7: 7 x 1 = 7, 7 x 2 = 14, ...).

  3. Use a do-while loop to implement a simple number-guessing game: the program picks a fixed secret number (say 42), and the user keeps guessing until they get it right. Print "Too high" or "Too low" as hints after each wrong guess.

  4. Write a nested loop that prints the following pattern for n = 5:

1
1 2
1 2 3
1 2 3 4
1 2 3 4 5

Summary

  • if / else if / else selects code paths based on conditions; always use braces around each block.
  • The ternary operator (cond) ? a : b is a compact two-branch conditional expression.
  • switch efficiently dispatches on integer/char values; always add break after each case unless fall-through is intentional.
  • while checks its condition before each iteration; do-while checks after — guaranteeing at least one execution.
  • for is the go-to loop when the iteration count is known; range-based for (C++11) cleanly iterates over arrays and containers.
  • break exits the innermost loop or switch; continue skips to the next iteration.
  • Avoid goto — prefer structured loops and functions.
  • Nested loops power grid algorithms, combinatorics, and classic techniques like the Sieve of Eratosthenes.
  • The Sieve of Eratosthenes finds all primes up to N in O(N log log N) — a standard competitive programming tool.
  • Off-by-one errors and missing break in switch are the two most common control-flow bugs.