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 towhile (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:
- Start with an array of
true(assume all numbers are prime). - For every number
pstarting from 2: ifpis still marked prime, mark all its multiples (starting fromp*p) as non-prime. - After processing, every index still marked
trueis 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
-
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.)
-
Read a positive integer
nand print an inverted triangle of stars:
*****
****
***
**
*
-
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. -
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, ...). -
Use a
do-whileloop 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. -
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 / elseselects code paths based on conditions; always use braces around each block.- The ternary operator
(cond) ? a : bis a compact two-branch conditional expression. switchefficiently dispatches on integer/char values; always addbreakafter each case unless fall-through is intentional.whilechecks its condition before each iteration;do-whilechecks after — guaranteeing at least one execution.foris the go-to loop when the iteration count is known; range-basedfor(C++11) cleanly iterates over arrays and containers.breakexits the innermost loop orswitch;continueskips 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
breakinswitchare the two most common control-flow bugs.