The If Statement: A Visual, Hands-On Guide to Decisions in Code
Learn the if statement visually: if, else, else if, && and ||, short-circuiting, the classic bugs (including Apple's goto fail), guard clauses, switch and ?:, real branch-prediction benchmarks, and exercises with answers.

On this page
Every useful program makes decisions. A game checks whether you hit the enemy. A shopping site checks whether the item is in stock. Your phone checks whether the password is right. Behind all of these is the humblest, most important tool in programming: the if statement.
An if lets your program reach a fork in the road and choose a path. In this guide you'll see how it works step by step, chain decisions together, combine conditions, avoid the bugs that have bitten even Apple, and finish with exercises to practice on.
The examples are in C++, but the same if (...) { ... } else { ... } shape works almost unchanged in C, Java, C#, JavaScript, Go, PHP and many other languages. Near the end you'll see it in Python and JavaScript too.
A fork in the road
An if asks a yes-or-no question, called the condition. If the answer is true, the code inside the braces runs. If it's false, that code is skipped. Add an else and the program takes a different path instead of skipping:

Just if: do it or skip it
1#include <iostream>
2
3int main() {
4 int temperature;
5 std::cout << "Temperature outside (C): ";
6 std::cin >> temperature;
7
8 if (temperature < 15) {
9 std::cout << "Brrr! Bring a jacket.\n";
10 }
11
12 std::cout << "Have a nice day!\n";
13}Two sample runs. On a cold day:
Temperature outside (C): 10
Brrr! Bring a jacket.
Have a nice day!On a warm day, the jacket line is skipped, but "Have a nice day!" always runs, because it's outside the braces:
Temperature outside (C): 22
Have a nice day!if / else: one of two paths
1#include <iostream>
2
3int main() {
4 int number;
5 std::cout << "Pick a whole number: ";
6 std::cin >> number;
7
8 if (number % 2 == 0) {
9 std::cout << number << " is even.\n";
10 } else {
11 std::cout << number << " is odd.\n";
12 }
13}Pick a whole number: 7
7 is odd.Pick a whole number: 42
42 is even.% gives the remainder of a division, so number % 2 == 0 means "divides by 2 with nothing left over". Exactly one of the two blocks runs, every time. Never both, never neither.
Conditions: questions with a true-or-false answer
A condition is any expression that ends up true or false. Most are built with comparison operators:
| Operator | Means | Example | With age = 15 |
|---|---|---|---|
== | equal to | age == 15 | true |
!= | not equal to | age != 15 | false |
< | less than | age < 13 | false |
<= | less than or equal | age <= 15 | true |
> | greater than | age > 18 | false |
>= | greater than or equal | age >= 13 | true |
else if: choosing between many paths
When there are more than two possibilities, chain conditions with else if. The program checks them from top to bottom and runs the block of the first one that's true. Everything after that is skipped:

Here's the original example from this article, now as a complete program:
1#include <iostream>
2
3int main() {
4 int number;
5 std::cout << "Enter a number between 1 and 100: ";
6 std::cin >> number;
7
8 if (number < 1) {
9 std::cout << "Your number is too small.\n";
10 } else if (number > 100) {
11 std::cout << "Your number is too big.\n";
12 } else {
13 // Both checks failed, so the number must be from 1 to 100
14 std::cout << "You are in the right range!\n";
15 }
16}Three sample runs, one for each path:
Enter a number between 1 and 100: -5
Your number is too small.Enter a number between 1 and 100: 150
Your number is too big.Enter a number between 1 and 100: 42
You are in the right range!Notice the else at the end has no condition. It doesn't need one: if the number isn't below 1 and isn't above 100, there's only one possibility left. The else is the last resort, the path taken when every other door is closed.
A grade ladder
Converting a score into a letter grade is the classic else-if ladder:
1#include <iostream>
2
3char letterGrade(int score) {
4 if (score >= 90) {
5 return 'A';
6 } else if (score >= 80) {
7 return 'B';
8 } else if (score >= 70) {
9 return 'C';
10 } else if (score >= 60) {
11 return 'D';
12 } else {
13 return 'F';
14 }
15}
16
17int main() {
18 int scores[] = {95, 83, 71, 64, 42, 90, 89};
19 for (int score : scores) {
20 std::cout << score << " -> " << letterGrade(score) << "\n";
21 }
22}195 -> A
283 -> B
371 -> C
464 -> D
542 -> F
690 -> A
789 -> BWhy doesn't 95 also get a B, since 95 is also >= 80? Because the chain stops at the first true condition. That's also why the order of the checks matters so much.
Order matters
Watch what happens when the same five tests are written in the opposite order:
1#include <iostream>
2
3char letterGrade(int score) {
4 if (score >= 60) { // Oops: the easiest test comes first
5 return 'D';
6 } else if (score >= 70) {
7 return 'C';
8 } else if (score >= 80) {
9 return 'B';
10 } else if (score >= 90) {
11 return 'A';
12 } else {
13 return 'F';
14 }
15}
16
17int main() {
18 int scores[] = {95, 83, 71, 64, 42};
19 for (int score : scores) {
20 std::cout << score << " -> " << letterGrade(score) << "\n";
21 }
22}195 -> D
283 -> D
371 -> D
464 -> D
542 -> F
It compiles without a single warning. The computer did exactly what it was told. This is a logic bug, the hardest kind to spot, which is why testing with several sample inputs matters.
Combining conditions: &&, || and !
Real decisions often depend on more than one thing. C++ has three logical operators to combine conditions:
| Operator | Name | True when… | Example |
|---|---|---|---|
&& | and | both sides are true | age >= 13 && hasPermission |
|| | or | at least one side is true | isWeekend || isHoliday |
! | not | the condition is false | !isLoggedIn |
The full truth table, with every combination:
a | b | a && b | a || b | !a |
|---|---|---|---|---|
true | true | true | true | false |
true | false | false | true | false |
false | true | false | true | true |
false | false | false | false | true |
Example: is it a leap year?
The leap-year rule is a great workout for logical operators. A year is a leap year if it divides by 4, except years that divide by 100, unless they also divide by 400:

1#include <iostream>
2
3int main() {
4 int years[] = {2023, 2024, 1900, 2000, 2100};
5 for (int year : years) {
6 bool isLeapYear = (year % 4 == 0 && year % 100 != 0) || year % 400 == 0;
7 std::cout << year << (isLeapYear ? " is" : " is not") << " a leap year\n";
8 }
9}12023 is not a leap year
22024 is a leap year
31900 is not a leap year
42000 is a leap year
52100 is not a leap yearShort-circuiting: the right side might never run
&& and || are lazy, in a good way. They evaluate the left side first, and if that already decides the answer, they skip the right side completely:

This is extremely handy for guarding against errors:
1#include <iostream>
2
3int main() {
4 int totalPoints = 120;
5 int gamesPlayed = 0;
6
7 // && stops at the first false: the division never happens when gamesPlayed is 0
8 if (gamesPlayed > 0 && totalPoints / gamesPlayed > 10) {
9 std::cout << "High scorer!\n";
10 } else {
11 std::cout << "Not enough games yet.\n";
12 }
13}Not enough games yet.Swap the two sides and the program would divide by zero, which in C++ is undefined behavior and on most computers crashes the program. Same conditions, different order, completely different result.
The classic if bugs
These three mistakes have cost programmers countless hours. Learn to spot them now and you'll save yourself the trouble.
Bug 1: = instead of ==
1#include <iostream>
2
3int main() {
4 int lives = 3;
5 if (lives = 0) {
6 std::cout << "Game over\n";
7 }
8 std::cout << "Lives left: " << lives << "\n";
9}Lives left: 0lives = 0 doesn't compare anything. It stores 0 in lives, and the whole expression has the value 0, which counts as false. So the player silently loses all their lives and the game-over message never appears. Fortunately, with warnings on (g++ -Wall), GCC 14 catches it:
1assign.cpp: In function 'int main()':
2assign.cpp:5:15: warning: suggest parentheses around assignment used as truth value [-Wparentheses]
3 5 | if (lives = 0) {
4 | ~~~~~~^~~Bug 2: missing braces
Without braces, an if controls only the next single statement, no matter how the code is indented:
1#include <iostream>
2
3int main() {
4 int balance = 50;
5 int price = 80;
6 if (balance < price)
7 std::cout << "Not enough money.\n";
8 std::cout << "Purchase cancelled.\n";
9 std::cout << "Thanks for shopping!\n";
10}That looks fine. Now imagine the balance were 100: "Purchase cancelled." would still print, because it isn't part of the if at all. The indentation lies. GCC warns about this too:
1indent.cpp: In function 'int main()':
2indent.cpp:6:5: warning: this 'if' clause does not guard... [-Wmisleading-indentation]
3 6 | if (balance < price)
4 | ^~
5indent.cpp:8:9: note: ...this statement, but the latter is misleadingly indented as if it were guarded by the 'if'
6 8 | std::cout << "Purchase cancelled.\n";
7 | ^~~The real-world version: Apple's "goto fail"
In February 2014, Apple shipped an urgent security update for iPhones, iPads and Macs. The cause was one duplicated line in the code that checks secure (HTTPS) connections:

The second goto fail; isn't controlled by any if, so it always runs, jumping past the final signature check. The result: devices could be tricked into trusting a fake website. It's exactly Bug 2, in software used by hundreds of millions of people.
Bug 3: wrong order in an else-if chain
You've already met this one: the "everybody gets a D" grade ladder above. When ranges overlap, the strictest test goes first.
Cleaner decisions
Guard clauses instead of deep nesting
Ifs inside ifs inside ifs quickly drift to the right of the screen, a shape programmers call arrow code. A guard clause handles each problem up front and leaves early:

1#include <iostream>
2#include <string>
3
4std::string checkout(bool loggedIn, int itemsInCart, bool hasAddress) {
5 if (!loggedIn) return "Please log in first.";
6 if (itemsInCart == 0) return "Your cart is empty.";
7 if (!hasAddress) return "Add a shipping address.";
8 return "Order placed!";
9}
10
11int main() {
12 std::cout << checkout(false, 2, true) << "\n";
13 std::cout << checkout(true, 0, true) << "\n";
14 std::cout << checkout(true, 2, false) << "\n";
15 std::cout << checkout(true, 2, true) << "\n";
16}1Please log in first.
2Your cart is empty.
3Add a shipping address.
4Order placed!Each line reads like a rule, and the happy path, "Order placed!", sits at the bottom where you'd expect it.
The ternary operator ? :
When all you need is to pick one of two values, the conditional (ternary) operator is a compact if/else:
1#include <iostream>
2#include <string>
3
4int main() {
5 for (int count = 0; count <= 2; count++) {
6 std::string word = (count == 1) ? "item" : "items";
7 std::cout << "You have " << count << " " << word << " in your cart\n";
8 }
9}You have 0 items in your cart
You have 1 item in your cart
You have 2 items in your cartRead condition ? a : b as "if condition, then a, otherwise b". Keep it for short choices; nested ternaries quickly become unreadable.
switch: one variable, many fixed values
When you compare one variable against a list of specific values, switch can be tidier than a long else-if chain:
1#include <iostream>
2
3int main() {
4 for (int day = 1; day <= 7; day++) {
5 std::cout << "Day " << day << ": ";
6 switch (day) {
7 case 6:
8 case 7:
9 std::cout << "weekend!\n";
10 break;
11 case 5:
12 std::cout << "almost there\n";
13 break;
14 default:
15 std::cout << "school day\n";
16 }
17 }
18}1Day 1: school day
2Day 2: school day
3Day 3: school day
4Day 4: school day
5Day 5: almost there
6Day 6: weekend!
7Day 7: weekend!default plays the role of else. Don't forget break: without it, execution "falls through" into the next case (which is exactly what case 6: does on purpose here, sharing the weekend message with case 7:).
Which one should you use?

Under the hood: why the processor guesses
Here's something most beginners never hear about. Modern processors work on many instructions at once, like an assembly line. When they reach an if, they don't wait to find out which way it goes; they guess and keep working. A right guess is free. A wrong guess means throwing away the half-finished work and starting over. This is called branch prediction.
We measured it for this article. The program adds up every value of 128 or more among 32 million random numbers from 0 to 255. Then it sorts the same numbers and runs the exact same if again:

With a real branch, the random data took 11 times longer: same numbers, same if, same answer. On sorted data, the if goes "no, no, no… yes, yes, yes", so the processor's guesses are almost always right. On random data it's a coin flip.
There's a twist: with normal optimization, GCC noticed the pattern and replaced the if with a special branch-free instruction (a conditional move), so the order stopped mattering. Compilers are clever, but they can't always do that, which is why fast code tries to make its decisions predictable.
A habit with the same spirit: common cases first
In an else-if chain where the cases don't overlap, putting the most common case first means fewer checks overall. We counted the checks for a million simulated chat messages:

The same idea in other languages
The if statement is nearly universal. Here's the range check in two other popular languages.
Python uses elif, a colon and indentation instead of braces:
1number = 42
2if number < 1:
3 print("Your number is too small.")
4elif number > 100:
5 print("Your number is too big.")
6else:
7 print("You are in the right range!")JavaScript looks almost identical to C++:
1const number = 42;
2if (number < 1) {
3 console.log("Your number is too small.");
4} else if (number > 100) {
5 console.log("Your number is too big.");
6} else {
7 console.log("You are in the right range!");
8}Both print You are in the right range!.
| C, C++, Java, C# | JavaScript | Python | |
|---|---|---|---|
| Else-if | else if | else if | elif |
| Block | { ... } | { ... } | indentation |
| And / or / not | && || ! | && || ! | and or not |
| Equality | == | === (recommended) | == |
Practice time
Try these before peeking at the answers. Copy each program into a file, compile with g++ -std=c++20 -Wall program.cpp, and run it.
Exercise 1: Predict the output (warm-up). What does this print?
1int coins = 7;
2if (coins > 10) {
3 std::cout << "rich ";
4} else if (coins > 5) {
5 std::cout << "comfortable ";
6} else if (coins > 0) {
7 std::cout << "scraping by ";
8}
9if (coins % 2 == 1) {
10 std::cout << "odd";
11}
12std::cout << "\n";Exercise 2: FizzBuzz. The most famous programming interview warm-up. For the numbers 1 to 15, print Fizz for multiples of 3, Buzz for multiples of 5, FizzBuzz for multiples of both, and the number itself otherwise. (Watch the order of your conditions!)
Exercise 3: Movie tickets. Write int ticketPrice(int age, bool isTuesday): under 3 is free; children under 12 and seniors 65 and over pay $8; everyone else pays $10 on Tuesdays and $15 on other days.
Exercise 4: Rock, paper, scissors. Write a function that takes two moves ('R', 'P' or 'S') and returns "Tie", "Player 1 wins" or "Player 2 wins". Try to do it with a single || chain instead of nine separate ifs.
Exercise 5: Triangle detective (challenge). Given three side lengths, print equilateral (all equal), isosceles (two equal), scalene (all different) or not a triangle (when any two sides add up to no more than the third).
Quick quiz
- What's the difference between
x = 5andx == 5? - In an if / else if / else chain, can two blocks run?
- In
if (b != 0 && a / b > 2), what happens whenbis 0? - What does
!(age >= 18)mean in plain English? - Why do style guides insist on braces even around one-line ifs?
Answers
Exercise 1: comfortable odd. The else-if chain stops at coins > 5. The second if is separate, not part of the chain, so it's checked too: 7 is odd.
Exercise 2: The "both" case must come first; otherwise 15 would match % 3 and print only Fizz.
1#include <iostream>
2
3int main() {
4 for (int number = 1; number <= 15; number++) {
5 if (number % 15 == 0) {
6 std::cout << "FizzBuzz\n";
7 } else if (number % 3 == 0) {
8 std::cout << "Fizz\n";
9 } else if (number % 5 == 0) {
10 std::cout << "Buzz\n";
11 } else {
12 std::cout << number << "\n";
13 }
14 }
15}11
22
3Fizz
44
5Buzz
6Fizz
77
88
9Fizz
10Buzz
1111
12Fizz
1313
1414
15FizzBuzzExercise 3: Guard clauses keep it short. Babies come first, since "under 12" would also match them.
1#include <iostream>
2
3int ticketPrice(int age, bool isTuesday) {
4 if (age < 3) return 0;
5 if (age < 12 || age >= 65) return 8;
6 if (isTuesday) return 10;
7 return 15;
8}
9
10int main() {
11 std::cout << "Baby, age 2: $" << ticketPrice(2, false) << "\n";
12 std::cout << "Kid, age 9: $" << ticketPrice(9, false) << "\n";
13 std::cout << "Adult, Tuesday: $" << ticketPrice(30, true) << "\n";
14 std::cout << "Adult, Saturday: $" << ticketPrice(30, false) << "\n";
15 std::cout << "Grandparent, age 70: $" << ticketPrice(70, true) << "\n";
16}1Baby, age 2: $0
2Kid, age 9: $8
3Adult, Tuesday: $10
4Adult, Saturday: $15
5Grandparent, age 70: $8Exercise 4: Handle the tie, then list the three ways player 1 can win. Anything else means player 2 won.
1#include <iostream>
2#include <string>
3
4std::string winner(char first, char second) {
5 if (first == second) return "Tie";
6 if ((first == 'R' && second == 'S') ||
7 (first == 'S' && second == 'P') ||
8 (first == 'P' && second == 'R')) {
9 return "Player 1 wins";
10 }
11 return "Player 2 wins";
12}
13
14int main() {
15 std::cout << "R vs S: " << winner('R', 'S') << "\n";
16 std::cout << "P vs S: " << winner('P', 'S') << "\n";
17 std::cout << "P vs P: " << winner('P', 'P') << "\n";
18}R vs S: Player 1 wins
P vs S: Player 2 wins
P vs P: TieExercise 5: Check for impossible triangles first, then the strictest shape (all three equal) before the looser one (any two equal).
1#include <iostream>
2#include <string>
3
4std::string triangleType(int first, int second, int third) {
5 if (first + second <= third || first + third <= second || second + third <= first) {
6 return "not a triangle";
7 }
8 if (first == second && second == third) return "equilateral";
9 if (first == second || second == third || first == third) return "isosceles";
10 return "scalene";
11}
12
13int main() {
14 std::cout << "3 3 3: " << triangleType(3, 3, 3) << "\n";
15 std::cout << "5 5 8: " << triangleType(5, 5, 8) << "\n";
16 std::cout << "3 4 5: " << triangleType(3, 4, 5) << "\n";
17 std::cout << "1 2 10: " << triangleType(1, 2, 10) << "\n";
18}13 3 3: equilateral
25 5 8: isosceles
33 4 5: scalene
41 2 10: not a triangleQuiz: (1) x = 5 stores 5 in x; x == 5 asks whether x equals 5. (2) No: at most one block runs, and with a final else, exactly one. (3) Nothing bad: b != 0 is false, so && short-circuits and the division never happens. (4) "Under 18". (5) Without braces, an if controls only the next statement, and misleading indentation (as in Apple's "goto fail") can hide that.
Cheat sheet
| I want to… | Write |
|---|---|
| Do something only sometimes | if (condition) { ... } |
| Choose between two paths | if (condition) { ... } else { ... } |
| Choose between many paths | if (a) { ... } else if (b) { ... } else { ... } |
| Require both conditions | a && b |
| Accept either condition | a || b |
| Flip a condition | !a |
| Pick one of two values | condition ? valueIfTrue : valueIfFalse |
| Compare one variable to fixed values | switch (variable) { case 1: ... break; default: ... } |
| Bail out early | if (problem) return; |
| Name a complicated condition | bool isLeapYear = ...; if (isLeapYear) { ... } |
Wrapping up
The if statement is how a program makes decisions, and almost every program is full of them:
ifruns a block only when its condition is true;elsegives the alternative path.else ifchecks conditions top to bottom, and the first true one wins. Order matters.&&,||and!combine conditions, and they short-circuit, which you can use to guard risky checks.- Watch for the classics:
=vs==, missing braces (remember "goto fail"), and overlapping conditions in the wrong order. Compiler warnings catch the first two. - Guard clauses,
? :andswitchkeep decision code flat and readable. - Predictable decisions are fast: in our test, the same if ran 11 times faster on sorted data.
Decisions plus repetition are the heart of programming. Next, see how ifs team up with loops in The For Loop and The While and Do-While Loops, meet the if statement's cousin in The Switch Statement (Case), and put them to work on data with C++ Arrays.
Want a more in-depth look into computer programming? Check out The Self-Taught Programmer.
Read next

How to Handle Errors in Go: A Hands-On Guide
Learn Go's everyday error pattern with runnable examples: return an error, check it, add context with %w, and recognize a missing file with errors.Is.

What Is the Swift Programming Language? A Simple Guide to the Language Behind iPhone Apps
A friendly, jargon-free guide to Swift, the programming language Apple created for iPhone, iPad and Mac apps. Learn why it replaced Objective-C, how optionals prevent crashes, how an app is made, and how fast Swift really is.

What Is the Rust Programming Language? A Simple Guide to the Fast Language That Won't Let You Crash
A friendly, jargon-free guide to Rust, the programming language that is as fast as C but stops whole families of memory bugs before a program ever runs. Learn about ownership, why tech giants are adopting it, and where it is used.
Comments
Be kind and specific. Comments with links from new accounts appear after a moderator reviews them.