Decision Making: if, else & switch
A program that always does the exact same thing regardless of its input isn't very useful — if/else and switch are how C branches its execution based on a condition.
Every program eventually reaches a fork in the road where it has to look at some value and pick which statements run next. C gives you three tools for that: if, which optionally chains into else and else-if for two-way and multi-way choices; switch, which jumps straight to a matching case out of a flat list of constants; and the compact ?: operator for picking between two values inline. This page works through all three in the order you'll actually need them — starting from the very idea of a 'true' value in C, through nested and chained conditions, into switch's fall-through behavior and the traps it sets for the unwary, and finishing with a worked program that leans on both if-else and switch together.
How C decides whether a condition is true
C has no dedicated boolean type built into if or switch conditions (before C99's _Bool, it had no boolean type at all). Instead, every condition is really just an integer expression: the value 0 means false, and literally any other value — 1, 2, -10, whatever — counts as true.
Only the middle block is skipped. The first and third both run, because C only cares whether the value is exactly zero or not — sign and magnitude don't matter beyond that.
C99 added _Bool (and bool, true, false through <stdbool.h>) purely for readability — writing bool found = 0; instead of int found = 0; makes intent clearer to a reader. Underneath, it's still the same zero-means-false, nonzero-means-true rule; bool is just an int-sized type restricted to storing 0 or 1.
if (1) {
printf("Runs — 1 is nonzero, so it counts as true.\n");
}
if (0) {
printf("Never runs — 0 is the only value C treats as false.\n");
}
if (-10) {
printf("Runs too — negative numbers are nonzero, so they are true as well.\n");
}The if statement
An if is a keyword, a condition in parentheses, and a block in braces. When the condition evaluates to true, the block runs; when it evaluates to false, the block is skipped entirely and the program moves on to whatever follows it.
The warning is conditional on temperature; the final line isn't inside the if's braces at all, so it always prints regardless of what temperature holds.
temperature > 35 is evaluated: 40 > 35 is true, so the if's block runs.
The warning message prints.
Execution then reaches the line after the closing brace and prints the forecast-complete message unconditionally.
if (condition) {
// runs only when condition is true
}#include <stdio.h>
int main() {
int temperature = 40;
if (temperature > 35) {
printf("Heat warning: stay hydrated.\n");
}
printf("Forecast check complete.\n");
return 0;
}Tracing an if statement's execution step by step
It helps to picture if as a simple fork with only one branch actually leading anywhere interesting — the other branch just skips straight past.
Whichever path is taken, both paths rejoin at the same point: the statement immediately after the if's closing brace.
evaluate condition
|
is it nonzero?
/ \
yes no
| |
run the {} skip straight
block past it
| |
+-----+-------+
|
continue with the
next statementif with a single statement
When an if's body is only one statement, C's grammar technically allows the braces to be dropped — the single statement right after the condition becomes the entire body.
This is legal, but it's worth treating braces as mandatory in your own habits rather than optional. It costs nothing to write them, and it protects you from the exact trap covered later on this page where a second line gets added underneath and silently ends up outside the if, no matter how the indentation makes it look.
if (x > 0)
printf("positive\n");if-else: exactly one of two paths runs
Adding else supplies a second block that runs precisely when the if's own condition was false. Between the two blocks, exactly one always executes — never both, and never neither.
number % 2 == 0 is false for 7, so the else branch runs and prints that 7 is odd.
#include <stdio.h>
int main() {
int number = 7;
if (number % 2 == 0) {
printf("%d is even\n", number);
} else {
printf("%d is odd\n", number);
}
return 0;
}evaluate condition
|
/ \
true false
| |
run if-block run else-block
| |
+------+-------+
|
continueelse-if chains for testing several ranges in order
Stacking else if lets one decision test a whole sequence of possibilities. Each condition down the chain is only checked if every condition above it was false, and the moment one matches, everything below it — including a trailing else — is skipped for good.
Writing the thresholds from highest to lowest lets each later condition mean 'and nothing higher matched' implicitly, without having to say so.
marks >= 90 tests 76 >= 90 — false, skipped.
marks >= 80 tests 76 >= 80 — also false, skipped.
marks >= 70 tests 76 >= 70 — true. grade becomes 'C', and the >= 60 branch plus the final else are both skipped without even being checked.
#include <stdio.h>
int main() {
int marks = 76;
char grade;
if (marks >= 90) {
grade = 'A';
} else if (marks >= 80) {
grade = 'B';
} else if (marks >= 70) {
grade = 'C';
} else if (marks >= 60) {
grade = 'D';
} else {
grade = 'F';
}
printf("Marks %d -> Grade %c\n", marks, grade);
return 0;
}Why the order of conditions in a ladder actually matters
An else-if ladder is only correct when its conditions are ordered so that an earlier branch never accidentally swallows a case meant for a later one. When ranges overlap — and 'marks >= 60' technically overlaps with 'marks >= 90', since 95 satisfies both — the first matching branch wins, permanently, even if a more accurate branch was sitting further down.
With marks = 95, the very first condition (marks >= 60) is already true, so grade is set to 'D' and the chain stops there — the >= 90 branch, which was actually the correct one, is never even reached.
This is exactly the bug that shows up when an else-if ladder is written smallest-threshold-first instead of largest-first: whichever overlapping condition sits highest in the chain wins by default, regardless of which one best describes the value. The fix is ordering, not extra conditions — put the most specific or highest threshold first, and each branch below it can safely assume everything above it already failed.
if (marks >= 60) {
grade = 'D';
} else if (marks >= 70) {
grade = 'C';
} else if (marks >= 90) {
grade = 'A';
}Nested if statements for multi-level decisions
Any if or else block can contain a full if/else of its own. Nesting makes sense when a second decision only becomes meaningful once an outer condition has already narrowed the situation down.
The outer if is a gatekeeper checking age alone; the inner if/else, which checks hasLicense, is only ever reached once that gate has already passed.
age >= 18 checks 20 >= 18 — true, so the outer block runs and the inner if is reached at all.
hasLicense is 1 (true), so the inner if's own block runs, printing the driving-allowed message.
Had age been under 18, the entire inner if/else would be skipped without ever being evaluated — the outer else would run instead.
#include <stdio.h>
int main() {
int age = 20;
int hasLicense = 1;
if (age >= 18) {
if (hasLicense) {
printf("Allowed to drive.\n");
} else {
printf("Old enough, but a license is still required.\n");
}
} else {
printf("Too young to drive.\n");
}
return 0;
}Combining conditions with &&, || and !
Three operators let you build one condition out of several smaller ones: && is true only when both sides are true, || is true when at least one side is true, and ! flips a condition from true to false or false to true.
&& requires both age >= 18 and age <= 60 to hold — 25 satisfies both, so the message prints.
|| only needs one side to be true — age < 18 already is, so the message prints without even needing to check age > 60.
!isLoggedIn reverses isLoggedIn's value: since isLoggedIn is 0 (false), !isLoggedIn is true, so the message prints.
int age = 25;
if (age >= 18 && age <= 60) {
printf("Eligible.\n");
}int age = 15;
if (age < 18 || age > 60) {
printf("Falls outside the working-age range.\n");
}int isLoggedIn = 0;
if (!isLoggedIn) {
printf("Please log in first.\n");
}Relational operators used inside conditions
Each of these operators produces 1 (true) or 0 (false), and that result is exactly what if inspects — stock <= 10 is 1 here, so the block runs.
< less than
> greater than
<= less than or equal to
>= greater than or equal to
== equal to
!= not equal toint stock = 5;
if (stock <= 10) {
printf("Reorder soon.\n");
}The classic = versus == trap inside a condition
= and == look similar and are both completely legal inside an if's parentheses, which is exactly why mixing them up is one of the most common beginner mistakes in C — and one the compiler will never flag, because it isn't actually wrong grammar, just the wrong operator.
x = 5 is an assignment, not a comparison. It stores 5 into x and then, because an assignment expression evaluates to the value it assigned, the if tests that 5 — which is nonzero, hence true. The block runs every time, regardless of what x held before, and x is now permanently 5.
The fix is almost always if (x == 10) with two equals signs. Treat any if condition that looks suspiciously 'always true' as a reason to double-check for a stray single =, since this compiles cleanly and gives no warning on many setups.
int x = 0;
if (x = 5) {
printf("this always runs\n");
}The dangling else: which if does an else actually belong to
When an if with no else of its own sits nested directly inside another if, and only one else follows, indentation can suggest a pairing that C doesn't actually use. The rule C follows is fixed: an else always binds to the nearest preceding if that doesn't already have its own else — indentation plays no part in the decision at all.
The layout suggests the else pairs with the outer if (temp > 30), but C actually attaches it to the inner if (humidity > 70), since that's the nearest if still missing an else.
What the indentation implies: if temp is above 30, check humidity; otherwise (temp not above 30), print the else message.
What C actually parses: if (temp > 30) { if (humidity > 70) { printf(...); } else { printf(...); } } — the else is entirely inside the outer if's block.
So the else message prints whenever temp > 30 is true but humidity > 70 is false — not whenever temp <= 30, which is what the layout on the page suggests at a glance.
Braces settle the ambiguity permanently, whatever the indentation ends up looking like afterward: if (temp > 30) { if (humidity > 70) { printf(...); } } else { printf(...); } forces the else onto the outer if, because the inner if is now a fully self-contained statement with its own closed block. Whenever a nested if has no else of its own and an outer else follows it, reach for braces instead of trusting how the lines are laid out.
if (temp > 30)
if (humidity > 70)
printf("Hot and humid\n");
else
printf("Temperature is not above 30\n");The switch statement
switch takes one expression, compares it against a list of constant case labels, and jumps directly to whichever one matches — instead of testing a series of separate if conditions one after another.
day is evaluated exactly once. C then looks for the case label matching that value and jumps straight there — case 3 — printing 'Wednesday' without touching any of the other cases at all.
#include <stdio.h>
int main() {
int day = 3;
switch (day) {
case 1:
printf("Monday\n");
break;
case 2:
printf("Tuesday\n");
break;
case 3:
printf("Wednesday\n");
break;
case 4:
printf("Thursday\n");
break;
case 5:
printf("Friday\n");
break;
case 6:
printf("Saturday\n");
break;
case 7:
printf("Sunday\n");
break;
default:
printf("Not a valid day number\n");
}
return 0;
}How switch works conceptually
Unlike an if/else-if chain, switch doesn't re-test expr against each case one comparison at a time in the source — conceptually it's a single lookup, and on many compilers a dense switch is actually compiled into a fast jump table rather than a sequence of comparisons.
switch (expr)
|
v
evaluate expr exactly once
|
+-- matches case 1? --yes--> run case 1's code --> break exits
|
+-- matches case 2? --yes--> run case 2's code --> break exits
|
+-- ... more cases ...
|
+-- no case matched -------> run default's code (if present)What can actually be used as a case label
Every case label must be a constant expression whose value is known at compile time — a literal number, a character constant, or an expression built purely from those (like 60 * 60), but never a variable, a range, or anything only known while the program is running.
case labels work just as well on a char as on an int, since a char is really a small integer type underneath — 'A' and 'B' are exactly the kind of compile-time constants a case label requires.
switch (grade) {
case 'A':
printf("Excellent\n");
break;
case 'B':
printf("Good\n");
break;
default:
printf("Keep working at it\n");
}break: how execution actually leaves a switch
break, placed at the end of a case's code, immediately exits the switch entirely — no further cases are checked or run, and control jumps straight to whatever comes after the switch's closing brace.
Whichever case matched, break sends execution straight to the printf after the switch — it never lingers inside the switch once its case's own code has finished.
switch (choice) {
case 1:
printf("Option one\n");
break;
case 2:
printf("Option two\n");
break;
}
printf("switch finished\n");Fall-through: what happens when break is left out
Without a break, execution doesn't stop when a case's code finishes — it simply continues into the very next case's statements, regardless of whether that next case's own value matches anything. This is called fall-through, and it's legal C, not an error.
n is 1, so execution jumps into case 1. Since case 1 has no break, it keeps running straight into case 2's printf, and then case 3's printf, only stopping once it reaches case 3's break.
case 1 matches n == 1 and prints 'One'.
No break follows, so execution falls straight into case 2's code and prints 'Two', even though n never equaled 2.
case 2 also has no break, so execution falls into case 3's code and prints 'Three'.
case 3's break finally stops the fall-through, and default is never reached.
This is exactly the shape of bug that shows up in a menu-driven calculator when a break goes missing from one arithmetic case: say a subtraction case computes result = x - y; but has no break — execution falls straight into the multiplication case next, silently overwriting result with x * y (and possibly falling further, into division, overwriting it again). Nothing crashes and nothing warns; the program just prints a confidently wrong answer, which is what makes a missing break here so easy to miss during testing.
#include <stdio.h>
int main() {
int n = 1;
switch (n) {
case 1:
printf("One\n");
case 2:
printf("Two\n");
case 3:
printf("Three\n");
break;
default:
printf("Other\n");
}
return 0;
}Intentional fall-through: grouping cases on purpose
Fall-through isn't only a bug waiting to happen — stacking several case labels back to back with no code (and no break) between them is a deliberate, common technique for giving multiple values the exact same behavior.
case 'A' and case 'B' share the exact same printf, because nothing — not even a statement — separates the two labels. Either grade reaches the same 'Good' line and then hits the same break.
When fall-through is intentional like this, it's good practice to make that explicit with a short comment such as // fall through right where a case is missing its break, so a future reader (including you) can tell at a glance that the missing break was a deliberate choice and not an oversight.
switch (grade) {
case 'A':
case 'B':
printf("Good\n");
break;
case 'C':
printf("Average\n");
break;
default:
printf("Needs improvement\n");
}default: the catch-all case
default runs when none of the listed case labels match the switch's expression at all — it plays roughly the same role a trailing else plays at the end of an if/else-if chain.
If input is anything other than 1 or 2, none of the earlier case labels match, so default's block runs instead.
switch (input) {
case 1:
printf("Start\n");
break;
case 2:
printf("Stop\n");
break;
default:
printf("Unrecognized command\n");
}Does break belong after default
default doesn't have to appear last — C allows it anywhere among the case labels — but by convention it almost always is written last, and when it is, there's nothing left below it for execution to fall into, so a trailing break after it has no practical effect.
Adding break after default anyway is a reasonable defensive habit rather than a wasted line — if another case is ever inserted below default later, that trailing break keeps the switch behaving correctly instead of silently falling through into the newly added code.
default:
printf("Unrecognized command\n");
break; // technically unreachable to fall past, but harmlessMultiple case labels sharing one block
The same stacking technique used for grouping A and B earlier scales up naturally to larger groups — any number of case labels can share one block of code, as long as nothing but the labels themselves sits between them.
month is 5, which falls under the case 4 / case 5 / case 6 group, so 'Second quarter' prints and the break exits before any other group is touched.
#include <stdio.h>
int main() {
int month = 5;
switch (month) {
case 1:
case 2:
case 3:
printf("First quarter\n");
break;
case 4:
case 5:
case 6:
printf("Second quarter\n");
break;
case 7:
case 8:
case 9:
printf("Third quarter\n");
break;
case 10:
case 11:
case 12:
printf("Fourth quarter\n");
break;
default:
printf("Not a valid month\n");
}
return 0;
}What kinds of values switch actually accepts
switch's expression, and every one of its case labels, must be of an integer-family type — int, char, or an enum — because matching works by exact equality against compile-time integer constants. Floating-point types are explicitly not allowed, no matter how the comparison is written.
This simply will not compile — switch requires an integer-family controlling expression, and float never qualifies, regardless of what the case values look like.
If you need to branch on a floating-point value, if/else-if is the only option — it can compare doubles and floats directly (with ==, <, or whatever the situation calls for), something switch is structurally unable to do.
float temperature = 98.6f;
switch (temperature) { // compile error: switch quantity is not an integer
case 98.6:
printf("Normal\n");
break;
}if-else or switch: choosing the natural fit
switch shines when a single variable is being compared against several discrete, known-in-advance values — a menu choice, a day number, a state in a state machine — since it reads as a flat list of cases instead of a repeated chain of comparisons, and it can only ever test for exact equality against integer-family constants.
if/else-if has none of those restrictions: its conditions can compare any type at all (including floats and strings via strcmp), test open-ended ranges, and combine several unrelated variables with && and ||. Grading a score against a range of thresholds, or classifying a value by several combined conditions, is naturally an if/else-if problem — not a switch problem, since switch has no way to express 'between 70 and 79' as a single case.
Worked example: choosing if-else for a temperature range
Classifying a temperature into cold, cool, warm, or hot needs open-ended range comparisons on a floating-point value — exactly what switch can't do at all, and exactly what if/else-if handles naturally.
28.5 fails the first two conditions, matches tempC < 30.0, and prints 'Warm' — a comparison switch could never express directly, since tempC is a double and the boundaries are ranges rather than fixed constants.
#include <stdio.h>
int main() {
double tempC = 28.5;
if (tempC < 10.0) {
printf("Cold\n");
} else if (tempC < 20.0) {
printf("Cool\n");
} else if (tempC < 30.0) {
printf("Warm\n");
} else {
printf("Hot\n");
}
return 0;
}Worked example: choosing switch for a menu choice
A small menu-driven calculator is the opposite situation: one int variable holding a small, fixed set of discrete options, which is exactly what switch is built for.
choice picks exactly one operation; each case computes it and breaks immediately, so the other three arithmetic cases never run for a given choice.
#include <stdio.h>
int main() {
int choice = 3;
double x = 8.0, y = 2.0, result;
switch (choice) {
case 1:
result = x + y;
break;
case 2:
result = x - y;
break;
case 3:
result = x * y;
break;
case 4:
result = x / y;
break;
default:
printf("Invalid menu choice\n");
return 1;
}
printf("Result: %.2f\n", result);
return 0;
}The decision-making landscape at a glance
Every construct on this page is really one idea — a decision — expressed in a different shape depending on what's being decided.
else-if chains, nested if/else, and switch's grouped cases are all just this same one-branch-runs idea, scaled up to handle more possibilities at once.
Decision
|
--------------------+--------------------
| | |
if if-else switch
(run a block, (exactly one of (one expression,
or skip it) two blocks runs) many discrete
| | constant cases)
| | |
else-if chains nested if/else grouped case labels,
(many ordered (a decision fall-through,
ranges) inside a defaultThe conditional operator ?: compared with if
?: is C's only ternary operator (the only one taking three operands), and the key difference from if is structural: ?: is an expression that produces a value, while if is a statement that only chooses which code runs. Because it's a value-producing expression, ?: can appear directly inside a larger expression — an assignment, a function argument, a printf — in a way an if statement simply cannot.
(a > b) ? a : b evaluates the condition once and produces a itself if it's true, or b if it's false — that produced value is what gets stored into max.
This is the equivalent if/else version — it takes four lines and a separate max declaration to do exactly what the one-line ternary above does in a single expression.
?: is genuinely useful for short, self-contained choices like picking a maximum or a plural suffix, but nesting several of them together to express a multi-way decision tends to hurt readability fast — once more than one condition is genuinely involved, a plain if/else-if chain is almost always clearer to read back later.
int a = 7, b = 12;
int max = (a > b) ? a : b;int max;
if (a > b) {
max = a;
} else {
max = b;
}Two more traps: mixing up && and ||, and indentation that lies
&& and || are easy to swap by accident, because English often uses 'and' and 'or' more loosely than C's operators actually mean. Wanting to reject any age outside the 18-to-60 range, for instance, is naturally misphrased as an && instead of an ||.
No value of age can ever be both less than 18 and greater than 60 at the same time, so this condition is always false — the message never prints, for any age at all. The intended operator was ||, exactly as used earlier on this page: age < 18 || age > 60.
A completely different trap comes from relying on indentation to show what belongs to an if, when there are no braces to actually enforce it.
The second printf is indented as if it's part of the if, but without braces, only the single statement immediately after the condition belongs to it. printf("done\n") is a completely separate statement that runs every single time, regardless of x.
Both traps are avoided the same way: use && and || deliberately by checking what each operand actually needs to be true, and wrap every if body in braces even when it's one line — exactly the habit recommended earlier on this page — so the code's real structure can never drift away from how it's indented.
int age = 15;
if (age < 18 && age > 60) {
printf("Outside the working-age range\n");
}if (x > 0)
printf("positive\n");
printf("done\n");Best practices for decision-making code that holds up
Nothing on this page is exotic — the constructs themselves are simple — but the handful of small habits below are what keep decision logic readable and correct as it grows past a couple of lines.
Wrap every if/else body in braces, even a single statement, so a line added later can never silently land outside the block it was meant to join.
In an else-if ladder with overlapping ranges, order the conditions deliberately — usually most specific or highest threshold first — since the first matching branch always wins, whether or not it's the best-fitting one.
Reach for switch, not a long else-if chain, once you're comparing one integer-family variable against several fixed, known values — it reads as a flat list rather than repeated comparisons, and some compilers turn it into a fast jump table.
Treat every case that's missing a break as something to double-check, not assume: either it's an intentional fall-through worth a // fall through comment, or it's a bug waiting to silently run the wrong code.
Keep nesting shallow. A decision nested three or four levels deep is hard to read back and easy to get wrong — an early return, or splitting the logic into a small helper function, usually flattens it out considerably.
Putting it together: if-else validation feeding a switch
Real programs frequently combine both tools in one flow: an if-else gate checks whether a value is even usable in the first place, and only once that gate has passed does a switch dispatch based on the value's specific contents.
The if-else handles a job switch fundamentally cannot: testing a range (choice < 1 || choice > 4) and bailing out early. The switch handles a job an if/else-if chain would express far more verbosely: dispatching to one of several fixed, known actions.
choice < 1 || choice > 4 checks whether 2 falls outside the valid 1-4 range — it doesn't, so the condition is false and the early return is skipped.
Execution reaches the switch with choice already guaranteed to be a valid, in-range value.
switch (choice) matches case 2 and prints 'You chose: Load Game', then break exits without checking case 3 or case 4.
Had choice been 0 or 7, the if-else would have printed the invalid-choice message and returned before the switch was ever reached at all — the two-level structure means the switch never has to defend itself against out-of-range input.
if executes a block of code only when a condition is true; else provides a fallback block for when it's false; else if chains multiple conditions in sequence. switch compares one value against a list of possible constant cases as a often-clearer alternative to a long if/else-if chain.
Almost every useful program needs to behave differently depending on its input or state — validating a password, applying a discount only above a certain amount, or picking a menu action based on user choice all require branching logic.
Form validation, game logic (is the player still alive?), menu-driven command-line tools, and state machines in embedded firmware all lean on decision-making constructs constantly.
if/else is a fork in a hiking trail with a sign: 'if it's raining, take the covered path; else, take the scenic one.' switch is more like a building directory: 'if you're going to floor 1, 2, or 3, here's each specific elevator — otherwise, take the stairs (default).' An if / else if / else chain
The program checks score against a descending set of thresholds and prints the matching grade.
The first condition, score >= 90, is checked: 85 >= 90 is false, so this branch is skipped entirely.
Control moves to the else if condition: score >= 80 checks 85 >= 80, which is true.
The block belonging to that true condition runs: printf("Grade: B\n") executes.
Because a matching branch already ran, the trailing else is skipped completely — only one branch in an if/else-if/else chain ever executes.
The same idea with switch switch jumps directly to the matching case label instead of testing a chain of separate conditions one by one.
switch (day) evaluates day once (3) and looks for a case label that matches it exactly.
case 3: matches, so execution jumps straight there and prints 'Wednesday'.
break; immediately after stops execution from continuing into case 4 (which doesn't exist here) or default — without it, execution would 'fall through' into whatever comes next.
default is never reached in this run because case 3 already matched — it only runs when none of the listed case values match.
switch (day) { case 1: printf("Mon"); case 2: printf("Tue"); break; } switch (day) { case 1: printf("Mon"); break; case 2: printf("Tue"); break; }
Without break after case 1, matching case 1 also runs case 2's code ('MonTue' both print) — this 'fall-through' is legal C, not an error, which is exactly why it's easy to miss.
if (x > 0) printf("positive\n"); printf("done\n");
if (x > 0) { printf("positive\n"); printf("done\n"); } Without braces, only the very next single statement belongs to the if — the second printf always runs regardless of x, even though the indentation visually suggests otherwise.
Any nonzero value is treated as 'true' in a C condition, and exactly 0 is 'false' — there's no strict boolean type required (though _Bool/bool from <stdbool.h> exists since C99 for clarity).
switch compares a single integer or char expression against constant case labels; each case needs an explicit break, or execution 'falls through' into the next case's code — sometimes a deliberate technique, more often an accidental bug.
default in a switch is optional but strongly recommended — it catches any value that didn't match any listed case, similar to a final else.
Braces {} around a single-statement if/else body are optional in C, but omitting them is a common source of bugs when a second statement is added later and mistakenly assumed to be 'inside' the branch.
Forgetting break in a switch case, causing execution to silently fall through into the following case's code — this is legal C, not a compiler error, which makes it easy to miss.
Writing if (x = 5) instead of if (x == 5) inside a decision statement (the same assignment-vs-comparison trap from the Operators topic) — it's worth re-flagging here since it's exactly where it bites hardest.
Adding a second statement after an if with no braces, assuming it's part of the conditional block when it actually always executes: if (x > 0) printf("a\n"); printf("b\n"); — 'b' always prints, regardless of x.
Using switch on a float or double — the C standard doesn't allow it at all (switch requires an integer-family or char expression), a mistake the compiler will catch immediately.
Always including default in a switch statement, even just to handle unexpected values defensively or assert they 'shouldn't happen'.
Using braces {} around every if/else body as a habit, even for single statements, to guard against the classic 'added a second line later' bug.
Reaching for switch instead of a long if/else-if chain specifically when comparing one variable against many discrete constant values — it's often clearer and some compilers optimize it into a fast jump table.
Intentional fall-through as a technique Occasionally, letting a case fall through into the next one deliberately combines handling for multiple values: case 'a': case 'e': case 'i': case 'o': case 'u': isVowel = 1; break; — this only works cleanly because all the empty cases share the same following code, and it's good practice to comment it explicitly (// fall through) so a reader knows it's intentional, not a forgotten break.
The ternary operator as a compact if/else condition ? valueIfTrue: valueIfFalse is a single expression equivalent to a simple if/else that assigns a value — int max = (a > b) ? a: b; — useful for short, single-value decisions, but nesting many ternaries together quickly becomes far less readable than a proper if/else chain.
What happens if you forget a break inside a switch case?
Execution 'falls through' into the next case's statements regardless of whether that case's own condition matches — this is legal C, so it compiles silently and only shows up as a runtime logic bug.
Can you use switch to compare strings in C?
No — switch only works with integer-family types (int, char, enum). Comparing strings requires an if/else-if chain using strcmp from <string.h>.
What value does C treat as 'false' in a condition?
Exactly 0. Every other value, including negative numbers, is treated as 'true'.
Why doesn't C have a distinct boolean type built into its if conditions, and what values count as true or false instead?
Historically C conditions are just integer expressions: the value 0 means false, and any nonzero value at all — including negative numbers — counts as true. C99 later added _Bool/bool through <stdbool.h> purely for readability, but it's still built on that same zero-versus-nonzero rule underneath.
What is the dangling-else rule, and why do braces resolve the ambiguity it creates?
When an if with no else is nested directly inside another if and only one else follows, C always attaches that else to the nearest preceding if that doesn't already have one — regardless of how the code is indented. Braces resolve the ambiguity because wrapping the inner if in its own { } makes it a fully closed statement, so any else written after that block can only belong to the outer if.
When is switch a more natural choice than an if-else chain, and what stops it from replacing every if-else chain?
switch reads best when one integer-family (or char/enum) variable is being tested for exact equality against several fixed, known-in-advance values, like a menu option or a day number — it's flatter to read than repeated comparisons and some compilers turn it into a fast jump table. It can't replace every if-else chain because it can't express ranges, can't combine multiple variables with && or ||, and can't accept a float, double, or string at all.
Why does fall-through exist as an actual feature of switch, rather than always being a bug to avoid?
Stacking case labels with no code between them, and no break, is exactly how multiple distinct values are made to share one identical block of behavior — grouping 'A' and 'B' into the same 'Good' outcome, for instance. It's a deliberate technique in that shape; it only becomes a bug when a break is left out of a case that was never meant to share code with the one below it.
What is the fundamental difference between the conditional operator ?: and an if statement, and what does that difference let ?: do that if can't?
?: is an expression — it evaluates to a value — while if is a statement that only chooses which code runs; if itself never produces a value. Because ?: produces a value, it can be embedded directly inside a larger expression, such as an assignment or a function argument (max = (a > b) ? a : b;), somewhere an if statement could never be written directly.
Write a C program to check whether a number entered by the user is positive, negative, or zero.
Three mutually exclusive possibilities map naturally onto an if / else if / else chain — each branch only runs when every condition above it has already failed, so exactly one message ever prints.
Write a C program to find the largest of three numbers using nested if-else.
The outer if narrows the comparison down to just one candidate (a or b), and the inner if then compares that survivor against c — two nested two-way decisions are enough to pick a winner out of three values.
Write a C program to check whether a given year is a leap year (divisible by 4, except centuries, unless also divisible by 400).
The rule is naturally a decision nested inside a decision: the outer if isolates every multiple of 4, and only then does the inner if apply the century exception, using && to require both the century condition and the not-divisible-by-400 condition together.
Write a C program that classifies a person's BMI (a double value) into "Underweight", "Normal", "Overweight", or "Obese" using if-else.
Because bmi is a floating-point value being tested against ranges rather than a fixed set of whole-number values, this has to be an if-else ladder — switch cannot compare a double at all. Writing the thresholds from lowest to highest works because each condition only runs once every condition above it has already failed.
Write a C program that converts a numeric month (1-12) into its season ("Winter", "Spring", "Summer", "Fall") using switch.
One int variable compared against a handful of known values is exactly what switch is built for, and stacking case labels with no statements between them (case 12: case 1: case 2:) groups several months under one season without repeating the printf.
Write a C program that acts as a simple calculator: read two numbers and an operator character (+, -, *, /), then use switch to perform the matching operation.
switch reads naturally here because op only ever needs to match one of a small, fixed set of characters; the division case adds an extra if check inside it since "divide by zero" is a condition switch itself has no way to express.
Write a C program that checks whether a character entered by the user is a vowel or a consonant using switch.
Listing both the lowercase and uppercase form of every vowel as its own case, with no break between them, lets all ten cases share the single "Vowel" printf; default then catches every character that didn't match any of them.
Write a C program that reads the three sides of a triangle and determines whether it is valid, then classifies it as Equilateral, Isosceles, or Scalene.
The validity check has to come first — any two sides must add up to more than the third side, or no such triangle can physically exist — before it's meaningful to ask what kind of triangle it is. The three classification branches are then ordered from most specific (all three equal) to least (none equal), since an equilateral triangle would otherwise incorrectly match the isosceles check too.
Write a C program that assigns a letter grade to a student based on their marks (90+: A, 80-89: B, 70-79: C, 60-69: D, below 60: F) using an if-else ladder.
Checking from the highest cutoff downward means each later condition is only ever reached once every higher grade has already been ruled out, so marks >= 70 only has to distinguish "at least C" from "below C" — it doesn't need to also re-exclude the A and B ranges itself.
Write a C program that reads a day number (1-7) and prints the corresponding day name using switch, handling an invalid number with default.
Every case ends with its own break, so exactly one day name prints and execution never falls through into the next case; default quietly handles any number outside 1-7 instead of silently doing nothing.
#include <stdio.h>
int main() {
int choice = 2;
if (choice < 1 || choice > 4) {
printf("Invalid choice: must be between 1 and 4.\n");
return 1;
}
switch (choice) {
case 1:
printf("You chose: New Game\n");
break;
case 2:
printf("You chose: Load Game\n");
break;
case 3:
printf("You chose: Settings\n");
break;
case 4:
printf("You chose: Exit\n");
break;
}
return 0;
}#include <stdio.h>
int main() {
int score = 85;
if (score >= 90) {
printf("Grade: A\n");
} else if (score >= 80) {
printf("Grade: B\n");
} else {
printf("Grade: C or below\n");
}
return 0;
}#include <stdio.h>
int main() {
int day = 3;
switch (day) {
case 1: printf("Monday\n"); break;
case 2: printf("Tuesday\n"); break;
case 3: printf("Wednesday\n"); break;
default: printf("Some other day\n");
}
return 0;
}