← Fundamentals
Guided lesson 5 questions · 6 languages

Conditions and truthiness

What an if tests, what counts as true, and which branch wins.

An if runs a block only when its condition is true. In C, a condition can be any number or pointer: a value equal to 0 counts as false, and every other value counts as true. When several tests are chained, the first true one wins.

Before you begin

Why this matters

C treats any number or pointer as a condition, and an assignment is a value like any other. So = written for == compiles and changes a variable. Knowing the rule lets you predict which block runs and recognize this bug when you see it.

What this helps you reason about

  • Predict which block of an if or an else-if chain runs.
  • Say which values a condition treats as false, and spot = written where == was meant.
  • Choose < or <= at the end of a range.

A jigsaw puzzle is either complete or it has pieces left. An if lets a program act on that. This lesson shows which block an if runs, and what C accepts as a condition: any number or pointer, where 0 means false.

Already know this? The short version
  • An if runs its block when its condition compares unequal to 0 and its else block otherwise; any number or pointer is allowed, so 0, 0.0, and NULL are false and everything else is true.
  • Comparisons and &&, ||, ! give the int 1 or 0. if (x = 0) compiles, stores 0, and is false; Clang warns by default, GCC only with -Wall or -Wparentheses, and the stepper hides warnings.
  • In an else-if chain the first true test wins, so put the most specific test first; without braces, an else belongs to the nearest if.
  • && and || stop at the first side that decides the result, which is why if (p && *p) is safe; < and <= differ only at the boundary value.

Go to the final check· Go to the practice tickets

Words in this lesson (6)
boolean
A value that means true or false. In C, a comparison gives the int 1 for true and 0 for false; the type bool holds true or false (from <stdbool.h> in C11; a keyword since C23).
if statement
A statement that runs a block of code when its condition is true. An else part, if there is one, runs when the condition is false.
condition
The test that decides whether an if runs its block, such as pieces_left == 0. Any number or pointer can be written there: 0 and NULL count as false, and every other value counts as true.
null pointer
A pointer that points to nothing, written NULL in code and shown as 0x0 by the debugger. getenv returns NULL for a variable that is not set.
else-if chain
A sequence of if … else if … else. The tests are checked from top to bottom; the first one that is true runs its block, and the rest are skipped. The final else runs when no test is true.
boundary value
The value where a range starts or ends, such as 1000 in “below 1000”. < and <= give different answers for this value only.

Examples are focused snippets; imports, enclosing functions, or classes may be omitted. C examples use C11; Haskell examples use Haskell 2010. Expand the notes for language-specific depth.

01 / If

What does an if statement do?

An if statement runs a block of code only when its condition is true. The condition is the test in parentheses after if. The block is the code between the braces that follow.

The else part is optional. Its block runs when the condition is false. Either way, the program then continues with the line after the whole if statement.

The usual condition is a comparison. As What is a value? showed, a comparison such as pieces_left == 0 gives the int 1 when it holds and 0 when it does not.

main.c
int pieces_left = 12;
if (pieces_left == 0) {
    printf("puzzle complete\n");
} else {
    printf("%d pieces left\n", pieces_left); // runs
}
printf("box closed\n"); // runs either way

An if runs its block when the condition is true and its else block when it is false; then the program continues after it.

Precisely: braces and else

Without braces, an if or an else controls one statement only. An else belongs to the nearest if before it that has no else, whatever the indentation says. Braces around every block avoid both surprises.

Experiment

Follow an if into one of its two blocks

The condition is computed first. When it is true, the block after if runs; when it is false, the else block runs. Exactly one of the two runs, and then the program continues after the whole if statement.

Why it matters To know which lines of an if run, compute its condition first, with the values the variables hold at that moment.

Predict which lines run and what is printed. Then change int pieces_left = 12; to int pieces_left = 0; and predict again.

#include <stdio.h>

int main(void) {
    setvbuf(stdout, NULL, _IOLBF, 0); // line by line, as in a terminal
    int pieces_left = 12;
    if (pieces_left == 0) {
        printf("puzzle complete\n");
    } else {
        printf("%d pieces left\n", pieces_left);
    }
    printf("box closed\n");
    return 0;
}
What to look for

At the if line, pieces_left shows 12. The next stop is the printf line, inside else: the stepper never stops on the "puzzle complete" line. The run prints 12 pieces left, then box closed. After the edit, the stepper stops on the "puzzle complete" line and skips the else block, and the run prints puzzle complete, then box closed.

Expected output

12 pieces left
box closed

02 / Conditions

What can be written as a condition?

C has no separate rule for conditions: any number or pointer can be one. A value equal to 0 counts as false, and every other value counts as true. A comparison gives 1 or 0, so it fits this rule. A null pointer, NULL, counts as false too, so if (name) asks whether the pointer name points to something.

The classic bug is = where == was meant. left = 0 is an assignment, and an assignment is also a value: the value stored. So if (left = 0) stores 0 in left, and the condition is 0, false. The else block runs, and left has lost its old value.

The compiler accepts this, but it can warn. Clang warns by default: using the result of an assignment as a condition without parentheses. GCC warns only with -Wall or -Wparentheses: suggest parentheses around assignment used as truth value. The stepper does not show compiler warnings.

main.c
int left = 12;
if (left) {
    // runs: 12 is not 0
    printf("%d pieces left\n", left);
}
if (left = 0) {
    // never runs: stores 0, and 0 is false
    printf("puzzle complete\n");
}
printf("%d\n", left); // 0

In C, 0 and NULL are false and every other value is true. An = in a condition compiles and changes the variable.

Coming from Java or Go: C accepts an int or a pointer as a condition, and an assignment compiles there too.

Coming from Python: The rule is like truthiness for numbers and pointers, but a C string is a pointer: if (s) is true for the empty string "", because the pointer is not NULL.

Precisely: scalar conditions

C11 6.8.4.1 requires the controlling expression of an if to have scalar type, an arithmetic type or a pointer, and runs the block when it compares unequal to 0. So 0.0 is false, and NaN, which is unequal to everything, is true. A struct cannot be a condition. Converting any nonzero value to bool gives 1. When an assignment in a condition is intended, extra parentheses, if ((n = next())), tell GCC and Clang so and silence the warning.

Experiment

Write = where == was meant

left = 0 is an assignment. It stores 0 in left, and its value is the value stored, 0. As a condition, 0 is false, so the else block runs, with left already changed.

Why it matters An = inside a condition compiles in C and changes a variable. Compile with warnings on (-Wall) and read them.

Predict what is printed. Then change if (left = 0) to if (left == 0) and predict again.

#include <stdio.h>

int main(void) {
    setvbuf(stdout, NULL, _IOLBF, 0); // line by line, as in a terminal
    int left = 12;
    if (left = 0) {
        printf("puzzle complete\n");
    } else {
        printf("%d pieces left\n", left);
    }
    return 0;
}
What to look for

At the if line, left shows 12. At the next stop, inside else, left shows 0: the condition itself stored 0 in left. The run prints 0 pieces left. After the edit, left still shows 12 inside else, and the run prints 12 pieces left. Clang and GCC can warn about the original line, as the section text shows, but the stepper does not show compiler warnings.

Expected output

0 pieces left

03 / Chains

Which branch runs when several tests are true?

An if statement can test several conditions in turn: if … else if … else. This is an else-if chain. The tests are checked from top to bottom. The first test that is true runs its block, and the rest of the chain is skipped, even tests that would also be true. The final else runs when no test was true.

So the order matters when two tests can both be true. A puzzle of 1500 pieces has at least 500 pieces and also at least 1000. If the test for 500 comes first, it catches every puzzle of 500 pieces or more, and the "huge" branch never runs. Put the most specific test first: the test that is true for the fewest values.

main.c
int pieces = 1500;
const char *size = "";
if (pieces >= 1000) {
    size = "huge";
} else if (pieces >= 500) {
    size = "large"; // 500 to 999
} else {
    size = "small";
}

In an else-if chain the first true test wins; put the most specific test first.

Experiment

Put a general test before a specific one

The tests are checked from the top, and the first true one decides. 1500 >= 500 is true, so the chain stops there. The test for 1000 is true as well, but it is never reached.

Why it matters When two tests in a chain can both be true, the earlier one wins. Put the most specific test first.

Predict what is printed. Then swap the first two branches, so that the test pieces >= 1000 with its "huge" line comes first, and predict again.

#include <stdio.h>

int main(void) {
    setvbuf(stdout, NULL, _IOLBF, 0); // line by line, as in a terminal
    int pieces = 1500;
    if (pieces >= 500) {
        printf("large\n");
    } else if (pieces >= 1000) {
        printf("huge\n");
    } else {
        printf("small\n");
    }
    return 0;
}
What to look for

At the if line, pieces shows 1500. The next stop is the printf line that prints large; the stepper never stops on the else if line or on the "huge" line. The run prints large. After the edit, the stepper stops on the "huge" line, and the run prints huge.

Expected output

large

04 / Combining

How do you combine two tests?

&& (“and”) is true when both tests are true. || (“or”) is true when at least one is true. ! (“not”) turns true into false and false into true. What is a value? introduced them; in a condition, they let one if test several things.

pieces >= 500 && pieces < 1000 is true from 500 to 999. At the end of a range, choose < or <= on purpose. They differ for one value, the boundary value itself: pieces < 1000 is false for 1000, and pieces <= 1000 is true. Read the rule you are writing: “up to 1000” and “below 1000” are different conditions.

&& and || also stop early. When the left side already decides the answer, the right side is not computed. This is called short-circuit evaluation, and a later topic covers it.

In C, &&, ||, and ! also give the int 1 or 0. !left is 1 when left is 0, and 0 for any other value.

main.c
int pieces = 1000;
printf("%d\n", pieces >= 500 && pieces < 1000);  // 0
printf("%d\n", pieces >= 500 && pieces <= 1000); // 1
printf("%d\n", !(pieces > 1000));                // 1

Combine tests with &&, ||, and !, and decide at each boundary whether the boundary value is inside: < leaves it out, <= keeps it.

05 / Try it

Can you explain a fresh example?

Decide which test in the chain is true first, then work out the last line. The next lesson, Function calls, shows how a program runs code that is written somewhere else.

main.c
#include <stdio.h>

int main(void) {
    int pieces = 300;
    int left = 0;
    if (left < 10) {
        printf("nearly done\n");
    } else if (left == 0) {
        printf("finished\n");
    }
    printf("%d %d\n", !left, pieces <= 300 && left == 0);
    return 0;
}

What is printed?

Reveal the reasoning

nearly done, then 1 1. left is 0, so the first test, left < 10, is already true. The chain stops there, and the test left == 0 is never checked, although it is true too. !left is 1, because left is 0. pieces <= 300 is true at the boundary value 300, and left == 0 is true, so && gives 1.

Practice tickets

  1. COND-101 · A self-timer set to 0 still waits 10 seconds → Free
  2. COND-102 · Children with a membership still pay at the door → Sign in · Pro
  3. COND-103 · A 300-times eyepiece pair is listed as high power → Sign in · Pro

Keep exploring

Function calls →

Primary references