Skip to content
C Codeloom
C++

C++ Control Flow: if, for, while, switch

Master C++ control flow with if-else, for and range-for loops, while, do-while, switch, and the modern init-statement syntax for conditions.

·5 min read · By Yash Kesharwani
Beginner 8 min read

What you'll learn

  • Use if-else and the C++17 init-statement form
  • Choose between for, range-for, while, and do-while
  • Write safe switch statements with fallthrough markers
  • Apply break and continue without making code unreadable
  • Reach for the right loop for each problem shape

Prerequisites

  • C++ basics — see /blog/cpp-variables-and-types

Control flow is how programs make decisions and repeat work. C++ inherits the C family syntax and adds a few modern conveniences that make code shorter and safer.

if and else

The condition must be a bool or something convertible to one. C++ has no truthy strings or lists; only numbers and pointers convert to bool.

int score = 82;

if (score >= 90)      std::cout << "A\n";
else if (score >= 80) std::cout << "B\n";
else                  std::cout << "C\n";

Always brace multi-statement branches. Skipping braces on single-line if invites the classic “added a second line and broke the logic” bug.

Init-statement if

C++17 lets you declare a variable inside the condition. The variable is scoped to the if/else block only, keeping helper values from leaking.

if (auto it = cache.find(key); it != cache.end()) {
    use(it->second);
} else {
    miss(key);
}

Use this whenever the value is only needed for the decision. It removes a temporary from the surrounding scope.

for loops

The classic for loop has three parts: init, condition, step.

for (int i = 0; i < 10; ++i) {
    std::cout << i << ' ';
}

Prefer ++i over i++ out of habit; for iterators it can be cheaper, and for int it costs nothing.

Range-for

For containers, range-for is almost always clearer.

#include <vector>

std::vector<int> nums{1, 2, 3, 4};

for (int n : nums)        std::cout << n;          // copies
for (const int& n : nums) std::cout << n;          // read-only ref
for (int& n : nums)       n *= 2;                   // mutate in place

Use const auto& for non-trivial element types and auto for cheap-to-copy ones.

while and do-while

while checks first, do-while runs the body at least once. Reach for do-while when you genuinely need that guarantee, such as reading input until validation passes.

int n{};
do {
    std::cout << "Enter a positive integer: ";
    std::cin >> n;
} while (n <= 0);

break and continue

break exits the innermost loop, continue skips to the next iteration. Used sparingly they read well; overused they turn loops into a tangle.

for (int i = 0; i < 100; ++i) {
    if (i % 2 == 0) continue;   // skip evens
    if (i > 25)     break;      // stop early
    std::cout << i << ' ';
}

switch

switch dispatches on an integral or enum value. Each case falls through to the next unless terminated by break or return. C++17 added [[fallthrough]] to mark intentional fallthrough so reviewers and compilers stop complaining.

enum class Status { Ok, Warning, Error };

void report(Status s) {
    switch (s) {
        case Status::Ok:
            std::cout << "ok\n";
            break;
        case Status::Warning:
            std::cout << "warn: ";
            [[fallthrough]];
        case Status::Error:
            std::cout << "issue detected\n";
            break;
    }
}

switch cannot dispatch on strings; for that, use an if-chain or a hash map of handlers.

Nested loops and labels

C++ has no labelled break. To exit nested loops cleanly, factor the inner work into a function and use return, or use a flag.

bool found = false;
for (int i = 0; i < rows && !found; ++i) {
    for (int j = 0; j < cols; ++j) {
        if (grid[i][j] == target) { found = true; break; }
    }
}

Refactoring into a function is almost always nicer:

std::optional<std::pair<int,int>> find(const Grid& g, int target);

Short-circuit evaluation

&& and || short-circuit: the right operand is not evaluated if the result is already decided. Lean on this to guard pointer dereferences.

if (p != nullptr && p->ready()) { /* safe */ }

The ternary

The ?: operator returns a value. Use it for short value selection, not for side effects.

int abs_x = x < 0 ? -x : x;

If either branch is more than a function call, write an if. Readability wins.

Putting it together

A small example that consumes input until end-of-stream, counting words longer than three letters:

#include <iostream>
#include <string>

int main() {
    int count = 0;
    for (std::string word; std::cin >> word; ) {
        if (word.size() <= 3) continue;
        ++count;
    }
    std::cout << count << " long words\n";
}

The for here uses no step clause — the input read is the implicit step. Compact and idiomatic.

What is next

You can now structure logic. Next, package it into reusable units in Functions and References, then learn Pointers and Memory for low-level work.

Wrap up

Prefer range-for over indexed loops when possible. Brace every branch. Use C++17 init-statements to scope temporaries tightly. And when nested loops sprawl, the answer is usually a small function, not a clever break.