Last 30 Days
No notifications
So far our programs run top to bottom. Real programs need to choose what to do based on data. That's control flow.
C gives you four main tools:
| Tool | When to use |
if / else if / else | A small number of conditions |
switch / case | Many discrete values of one variable |
Ternary ?: | A simple value choice (one line) |
goto | Almost never (legacy) |
int marks = 78;if (marks >= 90) {
printf("Grade A\n");
} else if (marks >= 75) {
printf("Grade B\n");
} else if (marks >= 60) {
printf("Grade C\n");
} else {
printf("Try again\n");
}
Rules:
( ).{ } (braces are technically optional for one statement, but always use them — saves bugs).When you're checking ONE variable against many constant values:
int day = 3;
switch (day) {
case 1: printf("Monday"); break;
case 2: printf("Tuesday"); break;
case 3: printf("Wednesday"); break;
case 4: printf("Thursday"); break;
case 5: printf("Friday"); break;
default: printf("Weekend");
}> ⚠️ Don't forget break; — without it, execution falls through to the next case.
int max = (a > b) ? a : b;
printf("%s\n", (n % 2 == 0) ? "even" : "odd");Anything non-zero is true:
int n = 5;
if (n) { /* true */ }
if (!n) { /* false */ }
char *s = NULL;
if (s) { /* false (NULL == 0) */ }Control flow is what turns a list of instructions into a program. Without if, your code can do exactly one thing. With it, your code can react to anything.
Original C had no bool type. It used int:
0 is false-1)#include <stdbool.h>bool isReady = true;
if (isReady) { ... }
Under the hood, bool is just a tiny integer. The header is pure sugar.
C lets you skip braces for a single statement:
if (x > 0)
printf("positive\n");That works — but it has caused billions of dollars of bugs. The infamous Apple "goto fail" bug came from this. Just always use braces:
if (x > 0) {
printf("positive\n");
}It's two extra characters and it removes a whole class of bugs.
Without braces, an else binds to the nearest if:
if (a)
if (b)
do_x();
else
do_y(); // belongs to "if (b)", NOT "if (a)" — confusing!Braces fix it:
if (a) {
if (b) do_x();
else do_y();
}// ❌ Three independent checks — all run
if (score >= 90) printf("A");
if (score >= 75) printf("B"); // ALSO runs if score = 95!
if (score >= 60) printf("C");// ✅ Chained — only ONE branch runs
if (score >= 90) printf("A");
else if (score >= 75) printf("B");
else if (score >= 60) printf("C");
else printf("F");
The chained version is faster (early exit) and almost always what you want.
switch (expression) {
case constant1:
// code
break;
case constant2:
// code
break;
default:
// code
}Rules:
1. expression must be an integer-like type (int, char, enum). Cannot use strings or floats.
2. case labels must be constants known at compile time.
3. break exits the whole switch. Forget it and you fall through.
4. default is optional but recommended — handles unexpected values.
Sometimes fall-through is what you want — group cases together:
switch (ch) {
case 'a':
case 'e':
case 'i':
case 'o':
case 'u':
printf("vowel\n");
break;
default:
printf("consonant\n");
}| Use switch when | Use if when |
| Comparing one variable to many constants | Conditions involve ranges (x > 5) |
| All cases are integer constants | Conditions involve different variables |
| You want compiler optimization (jump table) | Conditions involve floats / strings |
C still has goto. Don't use it for control flow. The only legitimate modern use is breaking out of nested loops on error, common in kernel code:
for (...) {
for (...) {
if (error) goto cleanup;
}
}
cleanup:
free(buf);For everything else, goto makes code unreadable. Avoid it.
| Mistake | Bug |
if (x = 5) | Assignment, always true |
Missing break; in switch | Falls through to next case |
switch (3.14) | Compile error — switch needs int-like |
if (a < b < c) | Doesn't mean what you think — compares (a |
Comparing strings with == | Compares pointers, not text — use strcmp |
Master if/else and switch and you can express any algorithm in C.