c-foundations Lesson 7 20 min read

How Do I Make Decisions with If, Else, and Switch?

Branching logic, the dangling else, and why switch can't handle strings

Reading: C Text: Ch. 4 §1 (pp. 195–200), §3–4 (pp. 210–235), §5 (pp. 235–245)

After this lesson, you will be able to:

  • Write if, else if, and else chains for multi-branch decision logic
  • Identify the dangling else problem and prevent it with braces
  • Write switch statements with break and explain fall-through behavior
  • Explain why switch cannot be used with strings in C
  • Use the ternary operator (? :) for concise conditional assignments

Same Syntax, Different Dangers

Good news: C’s if/else looks almost identical to Java’s. Bad news: the differences are subtle and dangerous. C won’t stop you from writing if (x = 5) (assignment, always true) or leaving out braces and hitting the dangling else problem.

Let’s use these statements to build real logic — starting with a number classifier and ending with the water bill calculator you’ll need for Lab 4.


Branching in C

The if Statement

int temperature = 35;

if (temperature > 100)
{
    printf("Boiling!\n");
}

No surprises — same as Java. But always use braces, even for single statements:

The Dangling Else

// What does this do?
if (x > 0)
    if (x > 100)
        printf("Big positive\n");
else
    printf("Negative?\n");     // WRONG — this else belongs to the inner if!

Without braces, else attaches to the nearest unmatched if. The indentation is misleading.

The Trick: Always use braces, even for single-statement bodies. It prevents the dangling else bug and makes your code unambiguous. This is standard professional practice.

Check Your Understanding
In the dangling else example below, which if does the else belong to?
if (x > 0) if (x > 100) printf("big"); else printf("other");
A The outer if (x > 0) — so "other" prints when x is negative
B The inner if (x > 100) — so "other" prints when x is 1-100
C It's ambiguous — the compiler picks randomly
D It's a syntax error — you must use braces
Answer: B. In C (and Java), else always attaches to the nearest unmatched if. So "other" prints when x is positive but not greater than 100. If x is negative, nothing prints at all. The indentation can be misleading — the compiler ignores whitespace. Braces make the intent explicit and prevent this entire class of bugs.

if/else if/else Chains

Classify a number:

int num;
printf("Enter a number: ");
scanf("%d", &num);

if (num > 0)
{
    printf("%d is positive\n", num);
}
else if (num < 0)
{
    printf("%d is negative\n", num);
}
else
{
    printf("%d is zero\n", num);
}

From Java: This is identical to Java syntax. The only difference is that C’s condition can be any integer expression (zero = false, non-zero = true), while Java requires a boolean.

The switch Statement

For multiple discrete values:

char grade;
printf("Enter grade (A-F): ");
scanf(" %c", &grade);      // Note: space before %c eats whitespace

switch (grade)
{
    case 'A':
        printf("Excellent!\n");
        break;
    case 'B':
        printf("Good job!\n");
        break;
    case 'C':
        printf("Satisfactory\n");
        break;
    case 'D':
    case 'F':
        printf("See your instructor\n");
        break;
    default:
        printf("Invalid grade\n");
        break;
}

Common Pitfall: Forgetting break in a switch is one of the most common C bugs. Without break, execution falls through to the next case. This is sometimes intentional (like cases ‘D’ and ‘F’ above sharing the same action) but usually a bug.

Common Pitfall: C’s switch only works on int and char values. You cannot switch on strings, floating-point values, or other non-integer types. For string comparison, use if/else if chains with strcmp.

Check Your Understanding
What happens if you forget break after case 'A' in a switch statement?
A Compilation error — break is required after every case
B Only the code for case 'A' runs — the switch exits automatically
C Execution falls through into case 'B' (and beyond) until a break is reached
D The default case runs instead
Answer: C. Without break, C continues executing the next case's code regardless of whether it matches. This "fall-through" behavior is by design — it lets cases share code (like 'D' and 'F' above). But it's usually a bug. Compile with -Wall to get warnings about missing breaks.
Why does this matter?

Switch fall-through bugs are among the most infamous in software history. Apple’s “goto fail” SSL bug in 2014 was caused by a similar control flow mistake. Getting branching logic right isn’t academic — it’s the difference between secure and vulnerable software.

The Ternary Operator

A compact if/else for simple assignments:

int max = (a > b) ? a : b;

// Equivalent to:
int max;
if (a > b)
{
    max = a;
}
else
{
    max = b;
}

Water Bill Logic (Lab 4 Preview)

Lab 4 asks you to compute a water bill with tiered rates. The logic looks like:

double bill;

if (gallons <= 1000)
{
    bill = gallons * 0.006;
}
else if (gallons <= 2000)
{
    bill = 1000 * 0.006 + (gallons - 1000) * 0.008;
}
else
{
    bill = 1000 * 0.006 + 1000 * 0.008 + (gallons - 2000) * 0.010;
}

Each tier builds on the previous one. This is a classic if/else if pattern — and understanding how the tiers accumulate is the key to getting Lab 4 right.

Check Your Understanding
In the water bill example, a customer uses 1500 gallons. Which branch runs?
A The first branch: bill = gallons * 0.006
B The second branch: bill = 1000 * 0.006 + (gallons - 1000) * 0.008
C The third branch: the else clause for over 2000 gallons
D Both the first and second branches, since 1500 is also less than or equal to 2000
Answer: B. The if/else if chain is evaluated top-to-bottom. 1500 is not ≤ 1000 (first condition fails), but 1500 is ≤ 2000 (second condition passes). Only one branch runs in an if/else if chain — once a condition is true, the rest are skipped. Option D describes what would happen with separate if statements (no else), which is a common mistake.
Quick Check: What happens if you forget break in a switch case?

Execution falls through to the next case, running its code too. If you forget break after case ‘A’, entering ‘A’ would also run the code for case ‘B’ (and ‘C’, and so on until a break is hit or the switch ends).

Quick Check: Can you use switch with a string variable in C?

No. C’s switch only works with integer types (int, char, long, etc.). For strings, use if/else if chains with strcmp() from <string.h>.

Quick Check: What does the dangling else problem look like, and how do you prevent it?

Without braces, an else attaches to the nearest unmatched if, not the one you intended based on indentation. Prevent it by always using braces around if/else bodies, even for single statements.


Decisions Are the Same, Discipline Is Different

The syntax for decisions in C is almost identical to Java. The difference is discipline: C won’t catch = vs == mistakes, won’t enforce braces, and won’t warn about switch fall-through (without -Wall). Your safety net is consistent style — always brace, always use -Wall, always break.

Next: loops. You’ll learn for, while, and do-while — plus the input validation pattern you’ll use in every interactive program.

Big Picture: Decisions plus operators give you the ability to write programs that respond to input. Combined with loops (next lesson), you’ll have all the control flow tools needed for every lab in this course. The tiered water bill logic is the kind of real-world conditional reasoning that shows up everywhere — tax brackets, shipping rates, grade scales.