Control Flow
Reviewed & published by Brayan K
By the end of this lesson you'll be able to make your C++ programs decide what to do — branching with if/else if/else, choosing values with the ternary ?:, matching exact values with switch, and combining conditions with &&, || and !.
Part of the free C++ course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.
What You'll Learn
- Branch your code with if, else if and else
- Pick a value in one line with the ternary ?: operator
- Match exact values with switch / case / break / default
- Use fall-through deliberately (and avoid it by accident)
- Nest conditions and combine them with &&, || and !
- Scope a variable to one test with if (init; condition) (C++17)
💡 Real-World Analogy
Control flow is the signpost at a fork in the road. Your program walks up to a junction, reads a condition ("is it raining?"), and takes the matching path. An if/else if/else chain is a row of signs checked in order — you follow the first one that points your way and ignore the rest. A switch is the departures board at a station: it looks up your exact destination and sends you straight to the right platform. Without these signposts, the program can only ever walk in one straight line.
1. if / else if / else
An if statement runs a block of code only when a condition is true. The condition goes in (parentheses) and must evaluate to true or false. Add else for a fallback, and chain else if for extra options. The chain is checked top to bottom and the first true branch wins — every branch below it is skipped — so put your most specific conditions first. Read this worked example, run it, then you'll write your own.
#include <iostream>
using namespace std;
int main() {
int score = 82;
// An if statement runs its block ONLY when the condition is true.
// The condition lives in (parentheses) and must be true or false.
if (score >= 50) {
cout << "You passed!" << endl; // runs, because 82 >= 50 is true
}
// if / else — one of the two blocks ALWAYS runs.
if (score >= 90) {
cout << "Top of the class." << endl;
} else {
cout << "Not quite top, but well done." << endl; // this one runs
}
// if / else if / else — checked top to bottom; the FIRST true branch wins.
// Once one branch runs, the rest are skipped — so order matters.
if (score >= 90) {
cout << "Grade: A" << endl;
} else if (score >= 80) {
cout << "Grade: B" << endl; // 82 >= 80 -> this runs, A was skipped
} else if (score >= 70) {
cout << "Grade: C" << endl;
} else {
cout << "Grade: F" << endl; // the catch-all when nothing matched
}
return 0;
}
// ✅ Expected output:
// You passed!
// Not quite top, but well done.
// Grade: B⚠️ Always use braces {}. Without them, only the single line after the if is conditional — the next line runs every time, which is a classic, silent bug.
2. The Ternary Operator ?:
The ternary is a compact if/else that returns a value. The shape is condition ? valueIfTrue : valueIfFalse. Because it produces a value, you can assign it directly (string s = ok ? "yes" : "no";) or drop it inside a bigger expression. Reach for it on short either/or choices; if a branch needs several statements, a normal if/else reads more clearly.
#include <iostream>
#include <string>
using namespace std;
int main() {
// The ternary operator is a one-line if/else that RETURNS a value:
// condition ? valueIfTrue : valueIfFalse
int age = 20;
string status = (age >= 18) ? "Adult" : "Minor";
cout << "Status: " << status << endl; // Status: Adult
// Great for picking the bigger of two numbers without a temp if-block.
int a = 42, b = 17;
int largest = (a > b) ? a : b;
cout << "Largest: " << largest << endl; // Largest: 42
// You can drop it straight inside a larger expression.
int n = 7;
cout << n << " is " << ((n % 2 == 0) ? "even" : "odd") << endl; // 7 is odd
// Use it for SHORT either/or choices. If a branch needs several
// statements, a normal if/else is clearer — don't force the ternary.
return 0;
}
// ✅ Expected output:
// Status: Adult
// Largest: 42
// 7 is oddYour turn. Fill in the two ___ blanks using the hints in the comments, then run it.
#include <iostream>
#include <string>
using namespace std;
int main() {
// 🎯 YOUR TURN — replace each ___ then press "Try it Yourself".
int temperature = 30;
// 1) Use a ternary to set "advice" to "Hot" when temperature >= 25,
// otherwise "Mild".
string advice = ___; // 👉 (temperature >= 25) ? "Hot" : "Mild"
// 2) Use a ternary to store the absolute value of -8 in "abs".
int value = -8;
int abs = ___; // 👉 (value >= 0) ? value : -value
cout << "Advice: " << advice << endl;
cout << "Absolute: " << abs << endl;
// ✅ Expected output:
// Advice: Hot
// Absolute: 8
return 0;
}3. switch / case / break / default
When you're comparing one value against many exact options, a switch is cleaner than a long else if chain. It works with int, char and enum (not strings or ranges). Each case ends with break to jump out of the switch; default is the catch-all, like else. If you omit break, execution falls through into the next case — handy when you deliberately stack cases (case 6: case 7: sharing one block), but a bug when you forget it.
#include <iostream>
using namespace std;
int main() {
// switch compares ONE value against many exact cases.
// It works with int, char and enum — NOT with strings or ranges.
int day = 3;
switch (day) {
case 1:
cout << "Monday" << endl;
break; // break = stop here, jump out of the switch
case 2:
cout << "Tuesday" << endl;
break;
case 3:
cout << "Wednesday" << endl; // matches -> this runs, then break
break;
// Intentional fall-through: case 6 has no break, so when day is 6
// execution "falls" into case 7 and they share the same code.
case 6:
case 7:
cout << "Weekend!" << endl;
break;
default: // runs when NO case matched (like else)
cout << "A weekday" << endl;
}
// Same idea with a char.
char grade = 'B';
switch (grade) {
case 'A': cout << "Excellent" << endl; break;
case 'B': cout << "Good" << endl; break; // this runs
default: cout << "Keep practising" << endl;
}
return 0;
}
// ✅ Expected output:
// Wednesday
// GoodNow you try. This vending machine is missing the keyword that stops fall-through and the catch-all label. Fill in the two blanks:
#include <iostream>
using namespace std;
int main() {
// 🎯 YOUR TURN — a tiny vending machine. Fill in the blanks.
int choice = 2; // 1 = Water, 2 = Cola, anything else = unknown
switch (choice) {
case 1:
cout << "Dispensing Water" << endl;
___ // 👉 add the keyword that stops fall-through here
case 2:
cout << "Dispensing Cola" << endl;
break;
___: // 👉 the catch-all label for unmatched choices
cout << "Unknown choice" << endl;
}
// ✅ Expected output:
// Dispensing Cola
return 0;
}4. Nesting, Logic & if-with-Initializer
Real decisions often depend on several things at once. Combine conditions with the logical operators: && (AND — both must be true), || (OR — either is enough), and ! (NOT — flips true and false). You can also nest an if inside another to make a second decision only when the first passed. Finally, C++17's if-with-initializer lets you declare and test a variable together — if (int x = f(); x > 0) — keeping x scoped to just that if/else.
#include <iostream>
#include <string>
using namespace std;
// Returns how many items are left in stock for an order id.
int stockFor(int id) {
return (id == 7) ? 3 : 0; // only product 7 is in stock (3 left)
}
int main() {
int age = 22;
bool hasTicket = true;
bool isVip = false;
// Logical operators combine conditions:
// && AND -> true only if BOTH sides are true
// || OR -> true if EITHER side is true
// ! NOT -> flips true <-> false
cout << "=== Entry ===" << endl;
if (age >= 18 && hasTicket) {
// Nested if: a second decision INSIDE the first.
if (isVip || age >= 65) {
cout << "Welcome — priority lane!" << endl;
} else {
cout << "Welcome — general entry." << endl; // this runs
}
} else {
cout << "Entry denied." << endl;
}
// if-with-initializer (C++17): declare a variable, then test it, in one
// line. Pattern: if (init; condition). "left" only exists inside the if.
cout << "=== Stock ===" << endl;
if (int left = stockFor(7); left > 0) {
cout << "In stock: " << left << " left" << endl; // In stock: 3 left
} else {
cout << "Out of stock" << endl;
}
// 'left' is gone here — that tidy scoping is the whole point.
return 0;
}
// ✅ Expected output:
// === Entry ===
// Welcome — general entry.
// === Stock ===
// In stock: 3 leftCommon Errors (and the fix)
- Missing break (accidental fall-through): if a case has no break, its code and the next case's both run. Add break; to every case unless you mean to fall through.
- = instead of ==: if (x = 5) assigns 5 to x and is always true. Use == to compare; writing if (5 == x) turns the typo into a compile error.
- No default case: if no case matches and there's no default, the switch silently does nothing. Add a default: to catch unexpected values.
- Dangling else: an else binds to the nearest unmatched if, not the one you indented it under. Use braces {} on every nested if so the else pairs with the right one.
- "error: 'x' was not declared in this scope": a variable from an if-with-initializer or a case block only lives inside that block. Declare it outside if you need it afterwards.
Pro Tips
- 💡 Handle edge cases first: deal with the unusual inputs early with simple ifs, then write the normal path without deep nesting.
- 💡 Use switch for menus: it's ideal for processing numbered choices (1, 2, 3…) in a console app.
- 💡 Flatten deep nesting: 4+ levels of indentation is a smell — pull the inner logic into a small function.
- 💡 Scope tightly: prefer if (auto v = lookup(); v) so short-lived variables can't leak into the rest of your function.
📋 Quick Reference
| Concept | Syntax |
|---|---|
| If | if (cond) { ... } |
| If / else | if (cond) { ... } else { ... } |
| Else if chain | if (...) {} else if (...) {} else {} |
| Ternary | result = cond ? a : b; |
| Switch | switch (x) { case 1: ...; break; default: ...; } |
| Logical AND / OR / NOT | a && b | a || b | !a |
| If with initializer (C++17) | if (int x = f(); x > 0) { ... } |
Mini-Challenge: Ticket Price Calculator
No blanks this time — just a brief and a blank canvas (with an outline to keep you on track). Build it, run it, and check your output against the example in the comments. This combines an if/else if chain with a ternary, exactly like real code.
#include <iostream>
#include <string>
using namespace std;
int main() {
// 🎯 MINI-CHALLENGE: Ticket price calculator
// 1. Make an int "age" and a bool "isStudent".
// 2. Decide the price with if / else if / else:
// under 5 -> 0 (free)
// 65 and over -> 8 (senior)
// isStudent is true -> 10 (student)
// everyone else -> 15 (standard)
// 3. Print: "Price: $X"
// 4. BONUS: use a ternary to print "Child" when age < 18, else "Adult".
//
// ✅ Expected (age = 20, isStudent = true):
// Price: $10
// Adult
// your code here
return 0;
}🎉 Lesson Complete
- ✅ if / else if / else run the first branch whose condition is true
- ✅ The ternary cond ? a : b returns a value you can assign or print
- ✅ switch matches exact int/char/enum values; break stops fall-through; default is the catch-all
- ✅ Combine conditions with &&, || and !, and nest ifs for layered decisions
- ✅ if (init; condition) (C++17) scopes a variable to just that test
Practice quiz
In an if / else if / else chain, which branch runs?
- All branches whose condition is true
- Only the else branch
- The first branch whose condition is true
- The last branch whose condition is true
Answer: The first branch whose condition is true. The chain is checked top to bottom and the first true branch wins; the rest are skipped.
What is the shape of the ternary operator?
- condition ? valueIfTrue : valueIfFalse
- condition : a ? b
- if condition then a else b
- condition && a || b
Answer: condition ? valueIfTrue : valueIfFalse. The ternary is condition ? valueIfTrue : valueIfFalse and returns a value.
Which types can a switch statement compare against exact cases?
- Only std::string
- Only double
- Any type including ranges
- int, char and enum
Answer: int, char and enum. switch works with int, char and enum — not strings or ranges.
What happens in a switch case that has no break?
- The program crashes
- Execution falls through into the next case
- It jumps to default
- Nothing prints
Answer: Execution falls through into the next case. Without break, execution falls through into the next case's code.
What role does 'default' play in a switch?
- It is the catch-all when no case matched (like else)
- It runs first
- It must come before all cases
- It stops fall-through
Answer: It is the catch-all when no case matched (like else). default runs when no case matched, like an else clause.
Why does 'if (x = 5)' always run its block?
- 5 is special
- if always runs once
- = assigns 5 to x and the non-zero value is treated as true
- It compares x to 5 and they match
Answer: = assigns 5 to x and the non-zero value is treated as true. = is assignment; it stores 5 in x and any non-zero value counts as true.
What is the key advantage of the ternary over a plain if/else?
- It is faster at runtime
- It returns a value you can assign
- It allows more statements
- It works on strings only
Answer: It returns a value you can assign. The ternary returns a value, so you can assign it: string s = ok ? "yes" : "no";.
Given int score = 82, which grade prints in the lesson's if/else if chain (A>=90, B>=80, C>=70)?
- Grade: A
- Grade: C
- Grade: F
- Grade: B
Answer: Grade: B. 82 >= 80 is the first true branch, so Grade: B prints (A was skipped).
What does an if-with-initializer 'if (int x = f(); x > 0)' give you (C++17)?
- A faster loop
- x scoped to just that if/else
- A global variable x
- Automatic break
Answer: x scoped to just that if/else. The variable declared in the initializer only exists inside that if/else, keeping it tightly scoped.
When stacking 'case 6: case 7:' with one block and a break, what does it achieve?
- A compile error
- Only 7 runs the code
- Both values 6 and 7 share the same code
- It skips both cases
Answer: Both values 6 and 7 share the same code. Deliberate fall-through lets several case values share one block of code.
Continue this course
- Previous: Operators
- Next: Loops — Repeat code with for, while, and do-while loops
- Quick reference: C++ cheat sheet
Frequently asked questions
When should I use a switch instead of an if/else chain?
Use switch when you are comparing ONE variable against many exact, constant values (like menu numbers or a char grade). It is cleaner and shows your intent. Use if/else when you need ranges (score >= 80), combined conditions with && or ||, or anything other than an int, char or enum.
What is fall-through in a switch?
If a case has no break, execution continues into the next case. Usually that is a bug — you forget the break and two messages print. But it is also a deliberate trick: stacking case 6: case 7: with one block lets several values share the same code.
Why does if (x = 5) always run?
A single = is assignment, not comparison. if (x = 5) stores 5 in x and then tests that value, and any non-zero number counts as true in C++, so the branch always runs. Use == to compare. Putting the constant first — if (5 == x) — makes a typo a compile error.
Is the ternary operator just a shortcut for if/else?
Almost. The key difference is that the ternary RETURNS a value, so you can assign it: string s = ok ? "yes" : "no";. A plain if/else does not return anything. Keep the ternary for short either/or choices; if a branch needs several statements, a normal if/else reads better.
What is an if-with-initializer and why use it?
Since C++17 you can write if (int x = f(); x > 0) — declare x and test it in one line. The variable only exists inside the if/else, so it cannot leak into the rest of your function. It keeps short-lived variables tightly scoped and your code tidier.