Week 14 · Phase 2 — The Ancestry
if, else, and boolean logic — the decision atom of every program ever written.
Photo · Jens Lelie / Unsplash
You can now compute. You cannot yet decide. Right now, every C program you've written runs straight through, top to bottom, doing exactly the same thing every time. Real software branches. It says: if the user clicked here, then this; otherwise, that. If the file exists, open it; otherwise, error out. If the network is up, send the request; otherwise, queue it.
This is the conceptual atom of every algorithm ever written. Strip away the syntax of the most baroque language, and what's left is some assembly of if-this-then-that-else-the-other, repeated. The chef has exactly one tool for this: a conditional jump. Everything else is built from there.
ifif (condition) {
// runs only if condition is true
} else {
// runs only if condition is false
}
The condition is anything that produces a number. C's rule is simple: 0 is false; anything else is true. Including 1, -1, 42, 0.0001, the address of any allocated object, and the result of any comparison. The "boolean" idea is conceptual; under the hood it's just integer arithmetic.
The else branch is optional. if (x > 0) { ... } with nothing after it is a complete construct: do the body if true, do nothing if false.
#include <stdio.h>
int main(void) {
int score = 73;
if (score >= 90) {
printf("A\n");
} else if (score >= 75) {
printf("B\n");
} else if (score >= 60) {
printf("C\n");
} else {
printf("F\n");
}
return 0;
}
This prints C. The chef checks each condition top to bottom. The first one that's true wins; the rest are skipped. else if is just convention — there's no special keyword. It's literally else followed by if, and most C codebases write it as one line for readability.
Remember Week 4 — the chef only knows fetch/decode/execute, with a small vocabulary of jumps and comparisons. Here's roughly what the assembly looks like for one of those ifs:
// if (score >= 90) { ...A... } else { ...B... }
cmp w0, #90 // compare score to 90
b.lt .else_branch // if less, jump to else
// ... A code here ...
b .end // skip the else
.else_branch:
// ... B code here ...
.end:
// program continues here
One cmp (compare). One b.lt (branch-if-less-than). One unconditional b (branch). That's it. Every if/else in every program in every language compiles down to roughly this shape. Once you've seen it, you can never quite unsee it.
Boolean logic has rules. Three main operators (&&, ||, !) and a small finite truth table for each. Memorising these saves you time forever.
| A | B | A && B | A || B | !A |
|---|---|---|---|---|
| true | true | true | true | false |
| true | false | false | true | false |
| false | true | false | true | true |
| false | false | false | false | true |
The takeaways:
And the duality you'll find yourself using: De Morgan's Law. !(A && B) is the same as !A || !B. !(A || B) is the same as !A && !B. Whenever a long, complex boolean expression feels wrong, try negating it and using De Morgan to flip the whole thing inside out — sometimes the result is dramatically clearer.
condition ? a : bC has a tiny, useful shorthand for "pick one of two values based on a condition":
int larger = (a > b) ? a : b;
Read it: "if a is greater than b, then a, otherwise b". It's an expression, not a statement — meaning it produces a value you can store, return, or pass into something else. Used carefully, it makes code shorter without making it less readable. Used carelessly, it produces unreadable nests. Don't chain more than one or two.
if (x > 0) printf("positive\n"); with no braces — but only one statement is governed by the if. The infamous goto fail bug in Apple's SSL library was a duplicated line outside an if that was assumed to be inside it. Always use braces.==. 0.1 + 0.2 == 0.3 is false (Week 13). For floats, compare with a tolerance: fabs(a - b) < 1e-9.= and ==. Already covered last week. Stay vigilant.if (x = 5) is one. if (1) is another. Compilers will warn; please listen.An if in your source code is, surprisingly, expensive at the chef level — particularly inside tight loops. The chef has a branch predictor that guesses, before the comparison is done, which way the branch will go, so it can keep its pipeline full. When the predictor is right (>95% of the time, typically), branches cost almost nothing. When it's wrong, the chef has to throw away its half-cooked work and start over — a "branch misprediction" — and that's a 15-cycle penalty.
This is why high-performance numerical code (matrix multiplication, AI inference kernels) tries to be branch-free. The same calculation is done unconditionally for every element, and the result is selected with a single masked operation rather than a conditional. The predictor never has to guess. The chef never stalls. You burn a few extra cycles of arithmetic, but you save an enormous amount of pipeline state.
"Predictable, dumb code" is the actual key to peak performance. If you ever find yourself writing a tight loop with a complex if in the middle, ask whether you can lift it out, vectorise the whole thing, or rewrite it without branching. The chef will thank you.
The simplest, oldest control structure — and yet the place modern CPUs spend the most ingenuity trying to predict.
% and a chain of if / else if. (You'll also need a loop, which is next week.)scanf("%d", &x) to read input.cc -Wall -Wextra. The compiler will catch =/== confusion, missing braces, and a few other classics.An if/else chain is fine for a few choices. When you have many — twenty different keys on a keyboard, sixty-four chess pieces, ten different command codes — there's a cleaner construct for that.
Week 15 is The Elevator — switch statements, jump tables, and how a compiler turns "match this against twenty options" into a single instruction.
Photo free under the Unsplash license. Forest path · Jens Lelie.