Control Flow
How programs make decisions and repeat work — if, switch, while, for, and how to think about them as flowcharts.
So far our programs have executed top to bottom, one line at a time.
Real programs need to make decisions and repeat work. The
constructs that let them do so — if, switch, while, for,
break, continue, return — are collectively called control
flow.
A useful mental model: at every moment, the CPU is following exactly one path through your program. Control-flow statements are forks and loops in that path.
Sequential flow
The simplest flow is straight-line execution: one statement after another.
Every program starts this way at main's opening brace.
if and else: decisions
An if statement tests a condition (a bool expression) and runs
its block only when the condition is true. An optional else block
runs when the condition is false.
else can chain into another if, building a ladder:
Two beginner footguns:
=is assignment,==is comparison.if (x = 5)compiles (it assigns 5 to x, then tests truthiness), and it is almost never what you meant.- Always use braces. Without braces, only the next single
statement belongs to the
if. This is a famous source of security bugs (the "goto fail" incident in Apple's TLS code).
// dangerous:
if (logged_in)
grant_access();
audit(); // <-- always runs, regardless of logged_in!
// safe:
if (logged_in) {
grant_access();
audit();
}Boolean operators
Conditions can be combined with:
&&— logical AND (true only if both sides are true)||— logical OR (true if either side is true)!— logical NOT (flips true/false)
Both && and || are short-circuited: the right side is only
evaluated if the left side could not already determine the answer.
This is what lets you write if (p != nullptr && p->value > 0)
safely.
switch: multi-way dispatch
When the same expression should be compared against many specific
constant values, switch is clearer than a ladder of else ifs.
A few rules:
- Each
casemust be a constant known at compile time (an int, a char, or anenum). - Without
break, execution falls through to the next case. That is rarely what you want; almost always includebreak. defaulthandles any value not matched.
while: loop with a condition
A while loop runs its body as long as the condition is true. The
condition is checked before each iteration; if it's false the
first time, the body never runs at all.
A do { ... } while (cond); variant exists where the body runs
first, then the condition is checked. It is rarely needed but
useful for prompt-and-validate loops.
for: counting loops
The for loop bundles three pieces of a typical counting loop
(initialization, condition, increment) into a single header:
for (init; condition; update) {
body
}The variable declared inside the for header (here i) is scoped
to the loop only. After the loop, i is gone. Good — that's
exactly the smallest-scope principle we mentioned earlier.
Range-based for
For containers (and anything with .begin() / .end()), the
range-based for is cleaner:
Notes:
const std::string&says "I'll just read each element, by reference." Without the&, each element would be copied, which would be wasteful for big strings.- You can also use
auto:for (const auto& name : names) ...
break and continue
Inside any loop:
breakexits the loop entirely.continueskips the rest of the current iteration and jumps back to the condition (or to the update forfor).
Use these sparingly. A loop with three breaks and four continues
is usually a sign that the condition could be rewritten more
cleanly.
Infinite loops on purpose
Sometimes you do want a loop that runs forever and is exited by an internal condition. Two idiomatic forms:
while (true) { ... }
for (;;) { ... }Both compile to the same code. Use whichever you find clearer.
Mapping algorithms to control flow
When you turn an algorithm into code, every "for each" becomes a
loop, every "if/then" becomes an if, and every "do until" becomes
a while. Practice that translation by writing the algorithm in
English first.
Algorithm: "Print the first 10 Fibonacci numbers."
- Start with
a = 0,b = 1. - Print
a. - Compute
c = a + b. Seta = b,b = c. - Repeat steps 2–3 nine more times.
Common loop bugs
| Bug | What happens | Fix |
|---|---|---|
| Off-by-one | Loop runs one too few or one too many times. | Decide consciously: do you want < or <=? Test on small cases. |
| Infinite loop | Condition never becomes false. | Make sure the body actually modifies the variable in the condition. |
| Wrong loop variable updated | You typed j instead of i. | Use short, distinct names; let nested loops use i, j, k. |
| Mutating a container while iterating | Crash or weird output. | Build a new container or use index-based iteration. |
Challenges
Implement a loop from 1 to 15 (inclusive). For each number, print:
"FizzBuzz"if it is divisible by both 3 and 5,"Fizz"if it is divisible by only 3,"Buzz"if it is divisible by only 5,- otherwise the number itself.
Each output goes on its own line.
Read the integer n = 729384 (already provided) and print the largest of its decimal digits. For 729384 the answer is 9. Use a loop that strips one digit at a time with % 10 and / 10. Print just the digit followed by a newline.
Test your understanding
What is the difference between == and = in a condition?
They are interchangeable.
== compares two values; = assigns the right-hand side to the left-hand side.
= compares; == assigns.
Both compare values, but == is faster.
Why does C++ short-circuit && and ||?
For backwards compatibility with Fortran.
So the right-hand side is only evaluated when needed, which lets you write safe guards like p != nullptr && p->value > 0.
To make code run faster on multi-core CPUs.
It is a quirk inherited from C with no real benefit.
In a switch statement, what does it mean to "fall through" a case?
The switch matched no case and fell through to default.
The case threw an exception that fell through to a handler.
Without break, execution continues into the next case's body, running its code as well.
The compiler emitted a warning that the user can ignore.
What is the scope of i in for (int i = 0; i < 10; ++i) { ... }?
The entire main function.
The current file.
Only inside the loop header and body; i ceases to exist after the loop ends.
All functions called from inside the loop.
Next: how to package code into reusable units — functions.