Learn C Programming

Lesson 1 of 7 · Decisions

Module 5 · Decisions

if, else if, else: Choosing a Path

FreeReading

In this lesson

  • Write an if, an if else and an else if ladder, with braces around every body.
  • Trace a ladder with a table of inputs and the path each input takes.
  • Explain why a ladder's order decides its answers, and why one = in a condition is a bug.

In Module 4, Amara wrote the leap year rule as one expression, and it printed 1 or 0. Her calendar app wanted a sentence instead: "2024 has 366 days".

A 1 on the screen is not a sentence. To print one of two sentences, the program has to choose which line runs.

Every program so far ran every line, top to bottom, every time. Today the road forks.

if: a block that runs only when the answer is not zero

An if statement asks a question. It runs a block of code only when the answer is true. In C, "true" means "not zero", the Module 2 rule.

The if statement

if (condition) {
    statements
}
  • if is the keyword, and brackets always follow it.
  • condition is any expression. Zero means false; every other value means true.
  • The braces hold the body: the lines that run only when the condition is true.
  • After the closing brace the program carries on, whichever way the question went.
#include <stdio.h>

int main(void)
{
    int stock = 7;

    if (stock < 10) {
        printf("reorder\n");
    }
    printf("stock checked\n");
    return 0;
}
reorder
stock checked

Change the 7 to 12 and only the second line prints. The printf inside the braces is skipped; the one after them is not.

So an if does not end the program or choose an exit. It decides whether one block runs, and then the program continues.

else: exactly one of two paths

An else gives the false answer a block of its own. Exactly one of the two blocks runs: never both, and never neither.

#include <stdio.h>

int main(void)
{
    int year = 2024;
    int leap = (year % 4 == 0 && year % 100 != 0) || year % 400 == 0;

    if (leap) {
        printf("%d has 366 days\n", year);
    } else {
        printf("%d has 365 days\n", year);
    }
    return 0;
}
2024 has 366 days

The expression is Amara's from Module 4, unchanged. What is new is that its 1 or 0 now decides which printf runs.

So the leap year rule did not change at all. It stopped being an answer to print and became an answer to act on.

The else if ladder: the first true condition wins

Most real questions have more than two answers. C chains them with else if, and the chain is usually called a ladder.

The else if ladder

if (condition_1) {
    runs when condition_1 is true
} else if (condition_2) {
    runs when condition_1 was false and condition_2 is true
} else {
    runs when every condition above was false
}
  • C tests the conditions from the top, one at a time.
  • The first true one runs its block, and every test below it is skipped.
  • The last else is optional. Without it, a value that matches nothing runs nothing.
#include <stdio.h>

int main(void)
{
    int marks = 72;

    if (marks >= 80) {
        printf("distinction\n");
    } else if (marks >= 60) {
        printf("merit\n");
    } else if (marks >= 40) {
        printf("pass\n");
    } else {
        printf("fail\n");
    }
    return 0;
}
merit

A trace table is the tool for reading a ladder. One row per input, and a column for every test C actually evaluated.

marksTests evaluated, in orderPath taken
95>= 80 truedistinction
72>= 80 false, >= 60 truemerit
60>= 80 false, >= 60 truemerit
45>= 80 false, >= 60 false, >= 40 truepass
12all three falsefail, the else

Read the middle column for 72. The test >= 40 never ran. The ladder stopped at the first true answer.

The else if ladder as a flowchart A yes leaves the ladder; only a no reaches the next test marks >= 80 yes distinction no marks >= 60 yes merit no marks >= 40 yes pass no else: fail For marks of 72, the path is: no at the first test, yes at the second, and the third never runs.

Now the ordering rule. Bob, who always rushes, writes the same ladder starting from the easiest test.

#include <stdio.h>

int main(void)
{
    int marks = 95;

    if (marks >= 40) {
        printf("pass\n");
    } else if (marks >= 60) {
        printf("merit\n");
    } else if (marks >= 80) {
        printf("distinction\n");
    } else {
        printf("fail\n");
    }
    return 0;
}
pass

Trace 95 through it. The first test, >= 40, is true, so the ladder stops there. The same happens for 62 and for 100.

Every mark from 40 up passes the first test, so the merit and distinction blocks can never run. The compiler says nothing; it cannot know what Bob meant.

So the rule for any ladder is this. When one condition's range contains another's, the smaller range must come first. Here, "80 and above" sits inside "40 and above", so it goes higher up.

Any int is a condition, and 0 is false

The brackets after if do not need a comparison. Any expression with a number works, and only zero means no.

#include <stdio.h>

int main(void)
{
    int stock = 0;
    int n = -7;

    if (stock) {
        printf("in stock\n");
    } else {
        printf("sold out\n");
    }

    if (n % 2) {
        printf("%d is odd\n", n);
    }
    return 0;
}
sold out
-7 is odd

if (stock) means exactly if (stock != 0). The short form reads well for a count that answers "is there any".

The second test is subtler. -7 % 2 is -1, the Module 4 sign rule, and -1 is not zero. So the test is true for negative odd numbers too, where n % 2 == 1 would have been false.

So a condition is not a special kind of value. It is a number, and only zero says no.

One equals sign: the bug that compiles

= stores a value. == asks a question. Inside a condition, the first one is almost always a typing slip.

#include <stdio.h>

int main(void)
{
    int marks = 35;

    if (marks = 100) {
        printf("full marks\n");
    }
    printf("marks is now %d\n", marks);
    return 0;
}
full marks
marks is now 100

Two things went wrong at once. The assignment's value is 100, which is not zero, so the test is always true. And the student's real mark has been overwritten.

The Playground compiles this in silence, because it has no -Wall. A local gcc -Wall on GCC 12 says warning: suggest parentheses around assignment used as truth value [-Wparentheses].

So when an if is true for every input you try, look for a lone = first.

Braces on every body, and the bug that taught everyone why

C allows an if without braces when its body is a single statement. This track never uses that permission, and here is the reason.

In February 2014, Apple shipped a fix for its TLS code on iOS and macOS, logged as CVE-2014-1266. A function that verified a server's signature contained two identical goto fail; lines under an if with no braces. Only the first line belonged to the if. The second ran every time, jumped past the real check, and returned success. People called it "goto fail".

You can build the same trap with nothing but printf.

#include <stdio.h>

int main(void)
{
    int marks = 35;

    if (marks >= 40)
        printf("pass\n");
        printf("certificate printed\n");
    return 0;
}
certificate printed

The indentation says both lines belong to the if. The compiler ignores indentation, so only the first one does.

The Playground is silent again. A local gcc -Wall says warning: this 'if' clause does not guard... [-Wmisleading-indentation]. With braces, the question never arises.

So braces are not decoration. They are the only thing that tells C where a body ends.

Example 1: the smallest if that reads its input

One condition, one body, and nothing at all when the answer is no.

#include <stdio.h>

int main(void)
{
    int celsius = 0;
    scanf("%d", &celsius);

    if (celsius < 0) {
        printf("freezing\n");
    }
    return 0;
}
freezing

That output is for the input -3. For 5 the program prints nothing, which is exactly what it was asked to do.

Run in Compiler
Example 2: if else, with a negative input

Two paths, and the Module 4 remainder rule decides which one a negative number takes.

#include <stdio.h>

int main(void)
{
    int n = 0;
    scanf("%d", &n);

    if (n % 2 == 0) {
        printf("%d is even\n", n);
    } else {
        printf("%d is odd\n", n);
    }
    return 0;
}
-7 is odd

That output is for the input -7. Comparing against 0 works for both signs, because a remainder of -1 is still not 0.

Run in Compiler
Example 3: the ladder a beginner actually writes

A cinema ticket by age. A guard throws out impossible input first, then the bands run from the youngest up.

#include <stdio.h>

int main(void)
{
    int age = 0;
    scanf("%d", &age);

    if (age < 0) {
        printf("invalid age\n");
    } else if (age < 3) {
        printf("ticket 0\n");
    } else if (age < 12) {
        printf("ticket 150\n");
    } else if (age >= 60) {
        printf("ticket 200\n");
    } else {
        printf("ticket 300\n");
    }
    return 0;
}
ticket 200

That output is for the input 65. Here the tests use <, so "under 3" sits inside "under 12" and comes first. The rule is the same one, read from the other end.

Run in Compiler

Where this is used

  • Exam results. Bangladesh's education boards publish a grade table: 80 and above is A+, down to F below 33. Every program that prints it is an else if ladder, strictest band first.
  • Thermostats. The Linux kernel's step_wise thermal governor compares a temperature against trip points. A chain of if statements decides whether cooling goes up, down or stays.
  • Input validation. The nginx web server refuses a request body larger than client_max_body_size with status 413. One comparison, one branch, before any real work starts.
  • Login checks. OpenSSH's server counts failed password attempts and disconnects after MaxAuthTries of them. That is an if on a counter.

Common mistakes

1. One equals sign in the condition.

if (marks = 100) {
    printf("full marks\n");
}

Silent on the Playground. A local gcc -Wall says warning: suggest parentheses around assignment used as truth value [-Wparentheses]. The test is always true and marks is overwritten. Write ==. You will make this one when you type fast, because both keys are the same key.

2. A semicolon straight after the condition.

if (marks >= 40);
{
    printf("pass\n");
}

Silent on the Playground. A local gcc -Wall reports this 'if' clause does not guard... [-Wmisleading-indentation], and -Wextra adds suggest braces around empty body in an 'if' statement [-Wempty-body]. The semicolon is an empty body, so "pass" prints for every mark. Delete it; an if line never ends in a semicolon.

3. Writing the symbols in the order you say them.

if (marks =< 40) {
    printf("fail\n");
}

An error on every command line, the Playground included: error: expected expression before '<' token. The operator is <=, with the angle first. You say "less than or equal", and the symbols follow the words.

4. The widest test at the top.

if (marks >= 40) {
    printf("pass\n");
} else if (marks >= 80) {
    printf("distinction\n");
}

No message at any command line. The second block can never run, because every mark that reaches 80 already passed the first test. Put the smaller range first, and trace one input from each band before you trust it.

Brain teaser

Bob wants "b" when x is not positive. He writes the line the way it reads in his head.

#include <stdio.h>

int main(void)
{
    int x = -1;
    int y = 5;

    if (x > 0)
        if (y > 0)
            printf("a\n");
    else
        printf("b\n");

    printf("done\n");
    return 0;
}

It prints only done. Say why "b" never appears for an x of -1. Then answer the harder half: which values of x and y do print "b"?

An else has to belong to exactly one if. The indentation is Bob's opinion; the compiler has a rule of its own.

Exercise 1Easy

Alice prints report cards with the Bangladesh board table. 80 to 100 is A+, 70 to 79 A, 60 to 69 A-, and 50 to 59 B. Then 40 to 49 is C, 33 to 39 D, and 0 to 32 F.

Input. One line with one integer marks.

Output. One line with the grade, or invalid if marks is below 0 or above 100.

Constraints. -1000 <= marks <= 1000.

Sample. Input 79 gives A. Input 101 gives invalid.

#include <stdio.h>

int main(void)
{
    int marks = 0;
    scanf("%d", &marks);

    /* One guard for the impossible marks, then one rung per band. */

    return 0;
}

Graded as grade-bands. Every boundary in the table is a hidden test, on both sides.

Run in Compiler
Exercise 2Easy

Kenji ran three laps, and the leaderboard shows only his best score. Two laps can tie.

Input. One line with three integers.

Output. One line with the largest of the three.

Constraints. Each value is between -1000000000 and 1000000000.

Sample. Input 3 9 5 gives 9. Input 7 2 7 gives 7.

#include <stdio.h>

int main(void)
{
    int a = 0;
    int b = 0;
    int c = 0;
    scanf("%d %d %d", &a, &b, &c);

    /* Two separate ifs are enough. Equal values must not break it. */

    return 0;
}

Graded as largest-of-three. The largest value moves through all three positions in the hidden tests.

Run in Compiler
Exercise 3Medium

Amara's calendar needs a plain answer for any year: is it a leap year or not?

Input. One line with one integer y.

Output. One line: yes if y is a leap year, otherwise no.

Constraints. 1 <= y <= 9999.

Sample. Input 1900 gives no. Input 2000 gives yes.

#include <stdio.h>

int main(void)
{
    int y = 0;
    scanf("%d", &y);

    /* The Module 4 expression decides; an if else prints the word. */

    return 0;
}

Not graded on its own. The problems lesson's date-check uses this rule inside a bigger question.

Run in Compiler
Exercise 4Hard

Zara tests a geometry app with three lengths. Each length must be shorter than the other two added together, or there is no triangle. Equal to the sum is flat, and does not count.

Input. One line with three integers a, b and c.

Output. One line: not a triangle, or else equilateral, isosceles or scalene.

Constraints. 1 <= a, b, c <= 1000000000.

Sample. Input 2 2 3 gives isosceles. Input 1 2 3 gives not a triangle.

#include <stdio.h>

int main(void)
{
    int a = 0;
    int b = 0;
    int c = 0;
    scanf("%d %d %d", &a, &b, &c);

    /* Is it a triangle at all? Then the narrowest kind first. */

    return 0;
}

Graded as triangle-kind. The order of the rungs is the whole difficulty.

Run in Compiler

Common doubts

  • Do I really need braces when the body is one line?

    C does not require them. This track does, because the next line somebody adds will look guarded and will not be. Two characters are a cheap price for that.

  • Is else if a keyword of its own?

    No. It is an else whose body happens to be another if. Writing both words on one line is a layout habit, and every C programmer reads the ladder that way.

  • Why a ladder, and not several separate if statements?

    Separate statements are all tested, so more than one block can run. For marks of 95, three separate tests would print distinction, merit and pass. A ladder stops at the first true test, so exactly one block runs.

  • Is if (x) exactly the same as if (x != 0)?

    Yes, to the compiler. Use the short form for a flag or a count that answers "is there any". Use the long form when the zero is a real value you are comparing against.

  • Does a long ladder make a program slow?

    Not in any way you could measure yet. Lesson 5 shows what GCC actually builds from a condition, and it is often less than the source suggests.

Key takeaways

  • An if runs its body only when the condition is not zero.
  • An else gives the false answer its own body, and exactly one of the two runs.
  • A ladder tests from the top and stops at the first true condition.
  • A trace table shows every test that ran; when one range contains another, the smaller goes first.
  • A lone = inside a condition assigns, and the Playground stays silent about it.
  • Every body gets braces, even a body of one line.

Next, an if goes inside another if, and an else loses track of which one it belongs to.

End of lesson 1

Mark it done, and your progress moves with you.

Next: Nested Conditions and the Dangling else