C Part I · Module 4 · Decisions and Control Flow
if, else and Multi-Way Decisions
Every program you have written so far has had exactly one possible execution: the statements inside main ran from the first to the last, each of them once, in the order you wrote them. That is a severe limitation, and it is the one this chapter removes. An if statement lets the value of an expression decide which statements run — which means the program now has more than one possible path through it, and you have to be able to say which one was taken and why. Nothing in this chapter is a new idea about values. The condition is an ordinary expression of the kind Module 3 spent twelve chapters on, judged against zero by the rule Chapter 2.10 established. What is new is that an expression's value can now change what executes next.
Foundation17 min readCPart IFundamentalsControl Flow
Part I · Module 4 · Chapter 4.1 · if, else and Multi-Way Decisions
1. Every Program So Far Had Only One Path
Look back at any example from the last three modules. Declarations, assignments, a few printf calls, return 0. Run it a thousand times with the same starting values and you get the same thing a thousand times, because there was never a choice to make. Execution entered main at the top and left at the bottom, and every statement in between ran exactly once.
Real programs are not like that. A program that reads a sensor has to behave differently when the reading is out of range. A program that processes a packet has to behave differently when the checksum fails. In every case the shape is the same:
Some statements should run only under a condition.
C's basic tool for that is the if statement.
2. The Smallest if
#include <stdio.h>
int main(void)
{
int temperature = 85;
printf("checking\n");
if (temperature > 70) {
printf("too hot\n");
}
printf("done\n");
return 0;
}Output:
checking
too hot
doneThree things happened, and it is worth being precise about each.
The condition was evaluated. temperature > 70 is an expression — exactly the kind Chapter 3.5 introduced. With temperature holding 85, it produced the int value 1.
That value selected a path. Because the value was not zero, the statement after the condition ran.
Execution then continued past the whole if statement. printf("done\n") is not part of the if and runs either way.
Change temperature to 42 and the condition produces 0, the printf("too hot\n") is skipped, and the output is checking followed by done. The program text is identical; the path through it is not.
3. What the Condition Actually Is
Here is the rule, and it is smaller than most beginners expect:
if (expression) statementevaluates the expression. If the result compares unequal to zero, the statement runs. Otherwise it does not.
There is no separate notion of a "condition type" in C. The parentheses hold an ordinary expression, and its value is judged by the zero/non-zero rule from Chapter 2.10 — the same rule that governs &&, || and !.
That has a consequence you will meet in real code constantly: the expression does not have to be a comparison.
#include <stdio.h>
int main(void)
{
int error_count = 0;
int retry_count = 3;
if (error_count) {
printf("there were errors\n");
}
if (retry_count) {
printf("retries remain: %d\n", retry_count);
}
return 0;
}Output:
retries remain: 3if (error_count) means if error_count is not zero. if (retry_count) means if retry_count is not zero. Both are legal, both are idiomatic C, and both are judged by exactly the rule above.
Whether you should write them that way is a separate question. if (retry_count > 0) says what you mean more loudly, and costs nothing. The reason to understand the short form is that you will read it in other people's code, and misreading it as something special would be worse than writing it yourself.
4. else: the Other Path
if on its own gives you run this or do nothing. Attaching an else gives you run this or run that:
#include <stdio.h>
int main(void)
{
int temperature = 42;
if (temperature > 70) {
printf("too hot\n");
} else {
printf("within range\n");
}
return 0;
}Output:
within rangeThe guarantee an else gives you is worth stating explicitly, because a great deal of correct code depends on it:
Exactly one of the two branches runs. Not both, and not neither.
5. if Controls Exactly One Statement
This is the single most important structural fact in the chapter, and the one that explains a bug you are otherwise doomed to write.
The form is:
if (expression) statement
if (expression) statement else statementOne statement — not "the indented part", and not "everything until the blank line". C does not use indentation to decide what is controlled. Indentation is for humans and the compiler ignores it entirely.
So how do the examples above control two or three statements? Because of this rule:
A block — a sequence of statements wrapped in braces — is itself one statement.
The braces are what turn several statements into the single statement that if controls. (Braces also affect where names declared inside them are visible; that is a separate topic, and Chapter 5.6 owns it. Here, braces are purely about grouping.)
What goes wrong without them
#include <stdio.h>
int main(void)
{
int faults = 0;
if (faults > 0)
printf("fault detected\n");
printf("raising alarm\n");
printf("end\n");
return 0;
}The author plainly intended both messages to depend on faults > 0. The indentation says so. Here is what actually runs with faults holding 0:
raising alarm
endThe alarm was raised with no fault. The if controls the first printf only, because that is one statement and the rule above says one statement is all it gets. The second printf is simply the next statement in main, which runs unconditionally.
Modern compilers notice the mismatch between the indentation and the structure:
warning: misleading indentation; statement is not part of the previous 'if'
[-Wmisleading-indentation]That diagnostic is a warning, not an error — the program is perfectly legal C, it just does not do what the layout suggests. Chapter 1.5's habit of compiling with warnings enabled is what turns this from a silent logic bug into a message.
The fix is the braces:
if (faults > 0) {
printf("fault detected\n");
printf("raising alarm\n");
}end6. Multi-Way Decisions
Many decisions are not two-way. A level might be invalid, low, normal or high. C has no special construct for this, and does not need one — it falls out of what you already have.
#include <stdio.h>
int main(void)
{
int level = 55;
if (level < 0) {
printf("invalid\n");
} else if (level < 25) {
printf("low\n");
} else if (level < 75) {
printf("normal\n");
} else {
printf("high\n");
}
return 0;
}Output:
normalelse if is not a construct in C. There is no such keyword pair. What you are looking at is an else whose single controlled statement happens to be another if statement — which, as §5 established, is allowed, because an if statement is a statement. Written without the conventional formatting, the same code is:
if (level < 0) {
printf("invalid\n");
} else {
if (level < 25) {
printf("low\n");
} else {
if (level < 75) {
printf("normal\n");
} else {
printf("high\n");
}
}
}Everybody writes the first form, because the second drifts off the right-hand side of the screen for no benefit. But knowing they are the same thing tells you two useful facts:
The tests are tried in order, and the chain stops at the first match. level holding 55 is not less than 0, not less than 25, and is less than 75 — so normal prints and the final else is never reached. Once a branch is selected, none of the remaining conditions are evaluated at all.
A trailing else is optional. Without one, it is possible for no branch to run. With one, exactly one branch always runs. Which you want is a design decision, not a formatting one.
Chain order is part of the logic
Because the chain stops at the first match, reordering the branches can change the answer. This is not a style preference:
#include <stdio.h>
int main(void)
{
const int minimum_valid = 0;
const int maximum_valid = 4095;
const int warning_level = 3000;
const int critical_level = 3800;
int sensor_connected = 1;
int reading = 3900;
if (!sensor_connected) {
printf("no sensor attached\n");
} else if (reading < minimum_valid || reading > maximum_valid) {
printf("reading %d is outside the valid range\n", reading);
} else if (reading >= critical_level) {
printf("critical: %d\n", reading);
} else if (reading >= warning_level) {
printf("warning: %d\n", reading);
} else {
printf("normal: %d\n", reading);
}
return 0;
}Output:
critical: 3900Now swap only the critical_level and warning_level branches, changing nothing else:
warning: 3900The same reading, from the same sensor, with the same thresholds, has been downgraded from critical to a warning — because 3900 is greater than or equal to 3000, so the warning test matched first and the critical test was never evaluated. Overlapping conditions must be ordered from the most specific to the least specific, and in a threshold chain that means most severe first.
Notice also that the validity checks come before the classification checks. Deciding whether a value is meaningful before deciding what it means is a habit worth forming now; it is the same reasoning that makes condition order a correctness question in Chapter 3.6.
7. Two Traps Worth Knowing by Name
The dangling else
When an else could plausibly belong to either of two nested if statements, which one does it attach to?
#include <stdio.h>
int main(void)
{
int a = 0;
int b = 0;
if (a > 0)
if (b > 0)
printf("both positive\n");
else
printf("a is not positive\n");
printf("end\n");
return 0;
}The indentation claims the else belongs to the outer if. The author expected a is not positive. Here is what runs:
endNothing from the if printed at all. The rule is:
An
elseis associated with the nearest precedingifthat does not already have one.
So the else belongs to if (b > 0), not if (a > 0). Since a > 0 was false, the entire outer if statement — inner if, else and all — was skipped.
Clang says so directly:
warning: add explicit braces to avoid dangling else [-Wdangling-else]And the cure is the one from §5. With braces there is no ambiguity to resolve, because the inner if is closed before the else appears:
if (a > 0) {
if (b > 0) {
printf("both positive\n");
}
} else {
printf("a is not positive\n");
}This trap only exists for people who omit braces. That is the whole reason §5's rule is worth following mechanically.
Assignment where you meant comparison
Chapter 3.4 established something that makes this trap possible: assignment is an expression, and its value is the value assigned. Combine that with §3 — a condition is any expression — and if (status = 1) is perfectly legal C.
#include <stdio.h>
int main(void)
{
int status = 0;
if (status = 1) {
printf("status is 1\n");
}
printf("status now holds %d\n", status);
return 0;
}Output:
status is 1
status now holds 1Both lines are wrong, in different ways. status was 0, so the branch should not have been taken — but the condition was not a test at all. It assigned 1 to status, and the whole expression then had the value 1, which is non-zero, so the branch ran. And as a parting gift, the variable the program was supposedly inspecting has been modified.
The diagnostic names it precisely:
warning: using the result of an assignment as a condition without parentheses
[-Wparentheses]
note: use '==' to turn this assignment into an equality comparison8. The Mental Model to Keep
A condition is just an expression.
ifevaluates it, compares the result with zero, and uses that to choose which single statement runs next. A block is one statement.else ifis nothing but anelsewhose statement is anotherif.
Everything in this chapter is a consequence of those four sentences, including both traps.
9. What You Should Be Able to Say Now
- Before this chapter, every program had exactly one path;
ifis what introduces a choice. - An
ifcondition is an ordinary expression, judged by the rule that zero is false and anything else is true. - The condition does not have to be a comparison —
if (count)is legal and meansif (count != 0). ifcontrols exactly one statement; a braced block is one statement.- Indentation has no effect on what is controlled — only braces do.
elseguarantees that exactly one of the two branches runs.else ifis not a construct; it is anelsewhose statement is anotherif.- A chain is tried in order and stops at the first match, so overlapping conditions must go from most specific to least specific.
- An
elseattaches to the nearest precedingifthat does not already have one. if (x = 1)assigns and then tests;-Wparenthesescatches it.
10. Exercises
Exercise 1 — predict the output. Do not compile yet. Write down what each program prints, then check.
int value = 0;
if (value) {
printf("A\n");
}
if (value == 0) {
printf("B\n");
}
if (!value) {
printf("C\n");
}Exercise 2 — trace the chain. For level holding each of −5, 0, 24, 25, 74, 75 and 200, say which branch of §6's first chain runs. Two of those values sit exactly on a boundary; say which branch takes them and why.
Exercise 3 — find the bug. This is supposed to print the warning only for readings above the threshold. Say exactly what it prints when reading holds 10, and name the rule that explains it.
int reading = 10;
if (reading > 100)
printf("above threshold\n");
printf("logging the event\n");Exercise 4 — repair it. Fix Exercise 3 with the smallest change that makes the program match its evident intent, and say which compiler warning would have revealed the problem.
Exercise 5 — explain the silence. In §7's dangling-else program, nothing at all was printed by the if. Explain why, in terms of which if the else belongs to and which condition was false.
Exercise 6 — classify the mistake. A colleague writes if (ready = 1) and reports that the branch runs every time, whatever ready held before. Explain what the expression evaluates to, why that makes the branch unconditional, and what ready holds afterwards.
Exercise 7 — order the chain. A valve controller must report shut below 5, throttled below 50, and open otherwise, and must reject any value above 100 as invalid. Write the chain. Then say what goes wrong if the invalid check is placed last instead of first.
Exercise 8 — compare two versions. One programmer writes if (count != 0) and another writes if (count). Say whether the two differ in behaviour, then say which you would put in code intended to be read by somebody else, and why.
11. Summary
- An
ifstatement makes a statement run only when a condition holds — the first construct in this curriculum that gives a program more than one path. - The condition is an ordinary expression, compared with zero. Nothing about it is special except its position.
ifcontrols exactly one statement, and a block is one statement. Indentation controls nothing.elsesupplies the other path, and guarantees exactly one of the two runs.- An
else ifchain is nestedifstatements. It is tried in order and stops at the first match, so order is logic, not formatting. - The dangling else attaches to the nearest preceding
if. Braces make the question disappear. =is not==. Assignment in a condition is legal, assigns, and then tests the assigned value.
A two-way decision is now straightforward, and a multi-way decision costs you a chain. But an if statement is a statement — it chooses what to do, and it cannot be used where a value is needed. The next chapter introduces the one operator in C that makes a choice and produces a value, along with the type rules that decide what that value is.
Standards & specifications
- Governing standard
- ISO/IEC 9899 (the C language standard)(opens ISO/IEC (JTC 1/SC 22/WG 14) in a new tab)
Defines the C language itself — the type system, expression semantics, object model, storage duration and the behaviour categories a conforming implementation must honour. It is a FAMILY of editions (C89/C90, C99, C11, C17, C23) and this curriculum names the edition wherever a claim depends on one. It does not define any foreign-function interface to a hardware description language, and it does not define platform behaviour: what a memory-mapped access does on real silicon is the implementation's and the device's contract, not this standard's. Working drafts published by WG14 are freely available and are what most engineers read day to day; a draft is not the published standard.
This page also covers RTL structure, verification approach and debugging technique. Those are engineering practice built on the standard, not requirements the standard itself imposes.
Where this fits
Part of the C Programming curriculum.
