Jako Heiberg
HomeInsightsMusingsPortfolioAboutContact
Jako Heiberg

From DBase III+ to edge computing.
The journey continues.

Pages

  • About
  • Portfolio
  • Resume / CV
  • Insights
  • Musings
  • Archive
  • Contact

© 2026 Jako Heiberg · Cape Town, South Africa · UTC+2

Built with React + Cloudflare Workers. No PHP was harmed.

Back to Insights
Insights

Python From Scratch — Part 3: Booleans & Control Flow

Every `if` you write rests on a value that's either `True` or `False` — but `==` and `is` are not the same question, and the day you confuse them on the number 257 is the day you find out why. Booleans, truthiness, and Python's whitespace-is-the-syntax rule, up close.

August 12, 2026
8 min read
Python

Part 3 of 23 · Level: Beginner · From Hello World to Neural Networks

Every interesting program eventually has to make a decision: is the password right, is the cart empty, did the user actually type a number? Decisions are built out of one tiny, opinionated thing — a value that is either True or False. Get comfortable with that, and the branching almost writes itself.

Booleans: the two-value type

A boolean is Python's smallest type: it holds exactly one of two values, True or False. Mind the capital letters — true is just an undefined name, and Python will complain.

is_raining = True
has_umbrella = False

print(is_raining)          # => True
print(type(is_raining))    # => <class 'bool'>

You rarely type True/False by hand, though. Usually you get them by asking a question about your data — which is what comparisons are for.

Comparison operators

A comparison takes two values and hands you back a boolean:

print(3 < 5)        # => True
print(3 > 5)        # => False
print(10 == 10)     # => True   (equal?)
print(10 != 10)     # => False  (not equal?)
print(7 >= 7)       # => True
print(7 <= 6)       # => False

The one that trips up newcomers from other languages: == is comparison ("are these equal?"), while a single = is assignment ("make this variable hold that value"). Python actually protects you — writing if x = 5: is a syntax error, not a silent bug. Small mercy, gratefully accepted.

Comparisons work on strings too, ordered by Unicode code point (which is roughly alphabetical):

print("apple" < "banana")   # => True
print("Zoo" < "apple")      # => True   (uppercase sorts before lowercase)

And Python lets you chain them, the way maths does:

age = 25
print(18 <= age < 65)       # => True

That reads as "18 is less than or equal to age, and age is less than 65" — no need to spell out the and yourself.

== vs is: the gotcha nobody warns you about

Here is the one that burns people. == asks "are these two values equal?" is asks "are these two the exact same object in memory?" They sound interchangeable right up until they aren't.

a = [1, 2, 3]
b = [1, 2, 3]

print(a == b)   # => True   (same contents)
print(a is b)   # => False  (two different lists that happen to match)

a and b hold the same numbers, so == is happy. But they are two separate lists at two different addresses, so is says no.

Now the part that makes is genuinely treacherous for beginners: Python caches small integers (-5 to 256), so equal values in that range secretly share one object — and equal values outside it don't.

a = 256
b = int("256")          # built at runtime, so not a literal
print(a == b)           # => True
print(a is b)           # => True   (256 is cached — one shared object)

x = 257
y = int("257")
print(x == y)           # => True
print(x is y)           # => False  (257 isn't cached — two objects, same value)

If you ever "tested" identity with is on small numbers and it seemed to work, you were riding the cache — and it will betray you the moment a value crosses 256. Rule of thumb: use == to compare values; reserve is for identity — which in practice means comparing against the singletons None, True, and False.

result = None

if result is None:      # correct, idiomatic
    print("no result yet")

if result == None:      # works, but not the done thing
    print("please don't")

is None is the blessed way to check for None: it is faster, it can't be fooled by a class that redefines ==, and every Python reviewer expects it.

and, or, not

To combine booleans, Python uses actual English words instead of && and ||:

logged_in = True
is_admin = False

print(logged_in and is_admin)   # => False  (needs both)
print(logged_in or is_admin)    # => True   (needs either)
print(not is_admin)             # => True   (flips it)
  • and is True only if both sides are true.
  • or is True if at least one side is true.
  • not flips a boolean.

One detail worth learning early: these short-circuit. and stops the instant it hits something false; or stops the instant it hits something true — because the answer is already decided. That is not just a speed trick, it is a safety one:

name = ""

# name is empty (falsy), so Python never evaluates name[0],
# and this doesn't crash:
if name and name[0] == "A":
    print("starts with A")
else:
    print("no name, no crash")

Output:

no name, no crash

Put your cheap or protective check on the left, and you can safely guard the expensive or risky one on the right.

if / elif / else, and why indentation is the syntax

Now we spend those booleans. An if statement runs a block only when its condition is truthy:

temperature = 30

if temperature > 25:
    print("Warm out.")
    print("Wear shorts.")
elif temperature > 10:
    print("Bring a jacket.")
else:
    print("It's freezing.")

Output:

Warm out.
Wear shorts.

Read it top to bottom: Python checks each condition in order and runs the first block that is true, then skips the rest. elif ("else if") chains alternatives; else catches everything left over. Both are optional — a lone if is perfectly fine.

Here is the thing that makes Python Python: the indentation is the syntax. Most languages use { } to mark a block and treat whitespace as decoration. Python has no braces — the spaces in front of those print lines are what tell the interpreter "these belong to the if." Change the indentation and you change the meaning:

if False:
    print("skipped")
print("always runs")   # not indented, so not part of the if

Output:

always runs

Practical rules: use four spaces per level (the whole community does), stay consistent, and never mix tabs and spaces — Python will reject the file with a TabError. Your editor almost certainly handles this for you; let it.

Truthiness: values that act like booleans

if does not actually demand a True/False. It takes any value and decides whether it is "truthy" or "falsy." The falsy values are a short list worth memorising, because everything else is truthy:

  • False
  • None
  • 0 (and 0.0)
  • "" — the empty string
  • [], {}, () — empty list, dict, tuple
cart = []

if cart:
    print(f"{len(cart)} items")
else:
    print("Your cart is empty.")

Output:

Your cart is empty.

if cart: reads as "if the cart has anything in it." That is cleaner than if len(cart) > 0: and it is the idiomatic way to ask "is this empty?"

But there is a gotcha hiding in plain sight. Truthiness makes 0 and None look identical to an if, and sometimes they mean very different things:

def describe(count):
    if not count:
        return "nothing to report"
    return f"{count} things"

print(describe(0))      # => nothing to report
print(describe(None))   # => nothing to report

If 0 is a legitimate value (a real count of zero) and None means "we don't know yet," collapsing them with if not count: is a bug. When you specifically mean "is this missing," say exactly that: if count is None:.

The conditional expression (Python's ternary)

Sometimes a full if/else is overkill — you just want to pick between two values. Python has a compact one-line form that reads almost like English:

age = 20
label = "adult" if age >= 18 else "minor"
print(label)   # => adult

The shape is value_if_true if condition else value_if_false. Note the order: the result comes first, then the condition. It feels backwards for about a day, then you'll reach for it constantly:

n = 7
parity = "even" if n % 2 == 0 else "odd"
print(parity)   # => odd

Keep it to genuinely simple choices. If you catch yourself nesting these, that is your cue to go back to a proper if/elif/else — readability wins.

A first look at match / case

Python 3.10 added match, a tidier way to branch on the shape of a value than a tall stack of elifs:

def http_message(status):
    match status:
        case 200:
            return "OK"
        case 404:
            return "Not Found"
        case 500 | 503:            # match several values with |
            return "Server error"
        case _:                    # _ is the catch-all, like else
            return "Something else"

print(http_message(404))   # => Not Found
print(http_message(503))   # => Server error
print(http_message(999))   # => Something else

The _ case is the wildcard — it matches anything you didn't handle, playing the role of else. For a plain value check like this, an if/elif chain would do the same job; match really earns its keep later, when you start destructuring tuples and objects. For now, just know it exists and reads nicely.

Next up

You can now make decisions — but a program that decides just once isn't much of a program. In Part 4: Loops, we put these same booleans to work as conditions that repeat, teaching Python to do the tedious thing ten thousand times without complaint.

Jako Heiberg

Software developer with 40+ years of building things that work. Full-stack, FastAPI, React. Based in Cape Town, working remotely, worldwide.

Related Posts

Insights

Python From Scratch — Part 2: Variables, Numbers & Strings

`input()` hands you back the string "25", never the number 25 -- and that one fact is behind more first-week Python bugs than anything else. This part covers variables, numbers and strings, plus the type-conversion trick that stops `"3" + 3` from ruining your afternoon.

July 28, 2026
9 min read
Insights

Python From Scratch — Part 1: Hello, World

Getting Python installed and running is the part that quietly stops more beginners than loops, classes, or neural networks ever will. Part 1 takes you from nothing installed — on Windows, macOS, or Linux — to your first running program, with a warning about the apostrophe that bites on line one.

July 23, 2026
8 min read
Insights

FastAPI vs Flask: I've Used Both, Here's What They Don't Tell You

Flask will do exactly what you tell it to and nothing more, which sounds like a compliment until you realise how much you forgot to tell it. FastAPI has opinions, and after shipping production APIs with both, I've come around to thinking that's the point.

May 7, 2026
5 min read