Skip to content

Control_flow

Control flow statements determine the order in which statements are executed in your program. They allow your code to make decisions, repeat actions, and handle different cases. This chapter covers all the control flow mechanisms in C++.

┌─────────────────────────────────────────────────────────────┐
│ Control Flow Statements │
├─────────────────────────────────────────────────────────────┤
│ Selection → if-else, switch, ternary operator │
│ Iteration → for, while, do-while, range-based for │
│ Jump → break, continue, return, goto │
│ Exception → try, catch, throw │
└─────────────────────────────────────────────────────────────┘

The if statement executes code conditionally based on a boolean condition.

int age = 18;
if (age >= 18) {
std::cout << "You are an adult" << std::endl;
}
int age = 16;
if (age >= 18) {
std::cout << "You are an adult" << std::endl;
} else {
std::cout << "You are a minor" << std::endl;
}
int score = 85;
if (score >= 90) {
std::cout << "Grade: A" << std::endl;
} else if (score >= 80) {
std::cout << "Grade: B" << std::endl;
} else if (score >= 70) {
std::cout << "Grade: C" << std::endl;
} else if (score >= 60) {
std::cout << "Grade: D" << std::endl;
} else {
std::cout << "Grade: F" << std::endl;
}
int age = 25;
bool hasLicense = true;
if (age >= 18) {
if (hasLicense) {
std::cout << "You can drive" << std::endl;
} else {
std::cout << "You need a license" << std::endl;
}
} else {
std::cout << "You must be 18 to drive" << std::endl;
}
// Checking for null pointers
if (ptr != nullptr) {
// Use ptr safely
}
// Early return pattern
if (!isValid) {
return; // Exit early
}
// Guard clauses
if (condition1) {
// Handle case 1
return;
}
if (condition2) {
// Handle case 2
return;
}
// Continue with main logic

The switch statement selects one of many code blocks to execute based on a value.

int day = 3;
switch (day) {
case 1:
std::cout << "Monday" << std::endl;
break;
case 2:
std::cout << "Tuesday" << std::endl;
break;
case 3:
std::cout << "Wednesday" << std::endl;
break;
case 4:
std::cout << "Thursday" << std::endl;
break;
case 5:
std::cout << "Friday" << std::endl;
break;
default:
std::cout << "Weekend" << std::endl;
break;
}

Cases can “fall through” when there’s no break:

int grade = 85;
switch (grade / 10) {
case 10: // grade = 100
case 9: // 90-99
std::cout << "Grade: A" << std::endl;
break;
case 8: // 80-89
std::cout << "Grade: B" << std::endl;
break;
case 7: // 70-79
std::cout << "Grade: C" << std::endl;
break;
default:
std::cout << "Grade: F" << std::endl;
break;
}
#include <string>
std::string color = "red";
switch (color) {
case "red":
std::cout << "Stop!" << std::endl;
break;
case "yellow":
std::cout << "Slow down!" << std::endl;
break;
case "green":
std::cout << "Go!" << std::endl;
break;
default:
std::cout << "Unknown signal" << std::endl;
break;
}

Note: String switch requires C++17 and uses std::string_view internally.

SwitchIf-Else
Checks equality against multiple valuesCan use any boolean expression
More readable for many casesBetter for complex conditions
Works with integral types and stringsWorks with any type
May have fall-through behaviorNo fall-through

The for loop executes a block of code a specific number of times.

// for (initialization; condition; increment)
for (int i = 0; i < 5; i++) {
std::cout << i << " "; // 0 1 2 3 4
}

Breakdown:

  • int i = 0 - Initialization (runs once)
  • i < 5 - Condition (checked before each iteration)
  • i++ - Increment (runs after each iteration)
for (int i = 0, j = 10; i < 5; i++, j--) {
std::cout << i << " " << j << std::endl;
}
for (;;) {
// Runs forever (use break to exit)
if (shouldExit) break;
}
for (int i = 10; i > 0; i--) {
std::cout << i << " "; // 10 9 8 7 6 5 4 3 2 1
}

Iterates over all elements in a range:

std::vector<int> numbers = {1, 2, 3, 4, 5};
for (int num : numbers) {
std::cout << num << " ";
}

With auto:

std::vector<std::string> names = {"Alice", "Bob", "Charlie"};
for (const auto& name : names) { // const ref avoids copy
std::cout << name << std::endl;
}

With initializer (C++20):

for (auto vec = std::vector{1, 2, 3}; auto& val : vec) {
std::cout << val << std::endl;
}

The while loop repeats as long as a condition is true.

int count = 0;
while (count < 5) {
std::cout << count << " "; // 0 1 2 3 4
count++;
}

Flowchart:

┌──────────────┐
│ Start │
└──────┬───────┘
┌──────────────┐
│ count < 5? │──No──► (End)
└──────┬───────┘
│ Yes
┌──────────────┐
│ Print count │
│ count++ │
└──────┬───────┘
└──────► (back to condition)
int num = 1;
while (true) {
if (num > 10) break;
std::cout << num << " ";
num++;
}

The do-while loop always executes at least once, then checks the condition.

int num = 1;
do {
std::cout << num << " "; // Always prints at least once
num++;
} while (num <= 5);

Use case - User input:

int choice;
do {
std::cout << "Enter 1-3: ";
std::cin >> choice;
} while (choice < 1 || choice > 3);

Exits the innermost loop or switch:

for (int i = 0; i < 10; i++) {
if (i == 5) break; // Exit loop when i is 5
std::cout << i << " "; // 0 1 2 3 4
}

Skips the rest of the current iteration:

for (int i = 0; i < 5; i++) {
if (i == 2) continue; // Skip when i is 2
std::cout << i << " "; // 0 1 3 4
}

The goto statement jumps to a labeled statement. It’s generally discouraged because it can create “spaghetti code”:

// Avoid this:
for (int i = 0; i < 10; i++) {
for (int j = 0; j < 10; j++) {
if (someCondition) {
goto found; // Jump to label
}
}
}
found:
// Code continues here

Better alternatives: Use functions, break, or return.

Exceptions handle runtime errors gracefully.

#include <stdexcept>
try {
int age = -5;
if (age < 0) {
throw std::invalid_argument("Age cannot be negative");
}
std::cout << "Age: " << age << std::endl;
}
catch (const std::invalid_argument& e) {
std::cout << "Error: " << e.what() << std::endl;
}
catch (const std::exception& e) {
std::cout << "General error: " << e.what() << std::endl;
}
try {
// Code that might throw
}
catch (const std::runtime_error& e) {
// Handle runtime errors
}
catch (const std::logic_error& e) {
// Handle logic errors
}
catch (...) {
// Handle any other exception
std::cout << "Unknown error occurred" << std::endl;
}
double divide(int a, int b) {
if (b == 0) {
throw std::runtime_error("Division by zero");
}
return static_cast<double>(a) / b;
}
#include <iostream>
#include <random>
#include <cstdlib>
int main() {
// Generate random number between 1 and 100
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_int_distribution<> dis(1, 100);
int target = dis(gen);
int guess;
int attempts = 0;
std::cout << "Guess the number (1-100)!" << std::endl;
do {
std::cout << "Enter your guess: ";
std::cin >> guess;
attempts++;
if (guess < target) {
std::cout << "Too low! Try again." << std::endl;
} else if (guess > target) {
std::cout << "Too high! Try again." << std::endl;
} else {
std::cout << "Congratulations! You got it in "
<< attempts << " attempts!" << std::endl;
}
} while (guess != target);
return 0;
}
// Bad
if (condition)
doSomething();
doAnotherThing(); // Bug: always runs!
// Good
if (condition) {
doSomething();
}
// Traditional (error-prone)
for (int i = 0; i < vec.size(); i++)
// Better
for (const auto& item : vec)
// Bad
for (int i = 0; i < 100; i++)
// Good
const int MAX_ITERATIONS = 100;
for (int i = 0; i < MAX_ITERATIONS; i++)
switch (choice) {
case 1: handleOption1(); break;
case 2: handleOption2(); break;
case 3: handleOption3(); break;
default: showError(); break;
}
// Deeply nested (avoid)
if (condition1) {
if (condition2) {
if (condition3) {
// Main logic
}
}
}
// Flattened (prefer)
if (!condition1) return;
if (!condition2) return;
if (!condition3) return;
// Main logic
  • Use if-else for boolean conditions
  • Use switch when comparing one value against multiple constants
  • Use for loops when you know the number of iterations
  • Use while loops when the number of iterations is unknown
  • Use do-while when you need at least one iteration
  • Use break to exit loops and continue to skip iterations
  • Use exceptions for error handling, not control flow
  • Keep code flat and avoid deep nesting

Now let’s learn about functions, which allow you to organize code into reusable blocks.

Next Chapter: 07_functions.md - Functions and Modular Programming