Skip to content

Chapter 2: Control Flow - Making Decisions Like a Pythonista

You know how to make decisions in code. if this, else that. But Python does it differently, and once you see why, you'll appreciate the elegance.

Indentation: It's Not Optional, It's the Syntax

First things first. Python doesn't use curly braces or end keywords. Your indentation is your code structure.

if x > 5:
    print("x is big")
    print("really big")
print("this runs regardless")

That indentation? It's not for humans. It's the actual syntax. Mix tabs and spaces? Python will yell at you. Inconsistent indentation? Error. This seems strict until you realize you'll never have a debate about where to put braces or whether to use them for single-line blocks.

Use 4 spaces. Not tabs, not 2 spaces. Four spaces is the Python way.

The if-elif-else Dance

You've written this a million times, but here's the Python version:

age = 25

if age < 13:
    print("child")
elif age < 20:
    print("teenager")
elif age < 60:
    print("adult")
else:
    print("senior")

Notice elif, not else if. Python likes brevity when it doesn't hurt clarity. No parentheses around conditions either (you can add them, but why?).

Truthiness: What Counts as True?

Here's where Python gets interesting. Everything has a boolean value in a conditional context:

if "hello":  # non-empty string? True
if "":       # empty string? False
if [1, 2]:   # non-empty list? True
if []:       # empty list? False
if 42:       # non-zero number? True
if 0:        # zero? False
if None:     # None? Always False

This is called "truthiness." Empty containers are falsy. Zero is falsy. None is falsy. Everything else is usually truthy.

This lets you write clean code:

# Instead of this
if len(my_list) > 0:
    process(my_list)

# Write this
if my_list:
    process(my_list)

Shorter, cleaner, more Pythonic. But be careful - this can bite you when 0 is a valid value:

user_input = 0
if user_input:  # Oops! 0 is falsy, this block won't run
    print(f"You entered: {user_input}")

When zero matters, be explicit: if user_input is not None:.

The Ternary Operator: One-Liner if-else

Python has a ternary operator, but it reads differently than C-style languages:

# Other languages: condition ? value_if_true : value_if_false
# Python: value_if_true if condition else value_if_false

status = "even" if x % 2 == 0 else "odd"

It reads almost like English: "status is 'even' if x is divisible by 2, else 'odd'." Use it for simple assignments. Don't nest them - that way lies madness.

Match-Case: Python's Modern Switch

From Python 3.10 onwards, you've got match-case. It's not just a switch statement - it's pattern matching:

def describe_number(n):
    match n:
        case 0:
            return "zero"
        case 1 | 2 | 3:  # multiple values with |
            return "small"
        case n if n < 0:  # with guard condition
            return "negative"
        case _:  # default case
            return "other"

The _ is the wildcard - catches everything else. But here's where it gets powerful:

point = (0, 5)

match point:
    case (0, 0):
        print("origin")
    case (0, y):  # matches any point on y-axis, captures y value
        print(f"on y-axis at {y}")
    case (x, 0):
        print(f"on x-axis at {x}")
    case (x, y):
        print(f"somewhere at ({x}, {y})")

Pattern matching with destructuring. If you're on Python 3.10+, use it. If not, stick with if-elif-else.

Loops: The for and while Story

The for Loop: It's Always a For-Each

Python's for loop is different. There's no C-style for(i=0; i<10; i++). Every for loop iterates over a collection:

# Looping over a list
for item in [1, 2, 3]:
    print(item)

# Looping over a string
for char in "hello":
    print(char)

# The classic counter pattern
for i in range(5):  # 0, 1, 2, 3, 4
    print(i)

range() gives you a sequence of numbers. range(5) is 0 to 4. range(1, 10) is 1 to 9. range(0, 10, 2) is 0, 2, 4, 6, 8.

Want both index and value? Use enumerate():

fruits = ["apple", "banana", "cherry"]
for i, fruit in enumerate(fruits):
    print(f"{i}: {fruit}")

This is cleaner than maintaining a counter manually.

The while Loop: Same Old Friend

While loops work like everywhere else:

count = 0
while count < 5:
    print(count)
    count += 1

Nothing fancy. When you need to loop until a condition changes, use while. When you're iterating over something, use for.

Break, Continue, and the Else Clause Nobody Uses

break exits the loop. continue skips to the next iteration. You know this.

But here's Python's quirk - loops can have an else clause:

for i in range(10):
    if i == 5:
        break
else:
    print("loop completed without breaking")

The else runs only if the loop completes normally (no break). It's useful for search operations:

for user in users:
    if user.name == search_name:
        print(f"Found: {user}")
        break
else:
    print("User not found")

Most Python developers don't use this often. But when you need it, it's elegant.

Pass: The Art of Doing Nothing

Sometimes you need a placeholder. Python doesn't allow empty blocks, so you use pass:

if condition:
    pass  # TODO: implement this later
else:
    do_something()

It's literally a no-op. The code equivalent of "noted" in a conversation.


What You Just Learned: - Indentation is syntax, not style - Everything has truthiness (empty = false, non-empty = true) - elif, not else if - Ternary reads like English - match-case for pattern matching (Python 3.10+) - for loops always iterate over collections - Loops can have else clauses - pass when you need to do nothing

Next: we'll dive into lists, tuples, and the art of storing multiple things without losing your mind.