Python Control Flow: If Statements and Loops (2026)
🐍 Python Cluster — 3 of 15 · Fundamentals

Python Control Flow: If Statements and Loops (2026)

Python control flow is deceptively simple until a condition quietly does the wrong thing. A data cleaning function checks if discount: before applying it — meaning to skip rows where no discount was recorded. It also skips every row where the discount is legitimately 0, because in Python, 0 is falsy, exactly like None is. No error. No warning. Real zero-discount customers just silently vanish from the report. This guide covers if/elif/else, truthy and falsy values, for and while loops, and the loop-mutation bug that catches almost everyone at least once.

by Khalid Hussain Published Aug 4, 2026 🕐 12 min read

Quick Answer: Python Control Flow

▶ Direct Answer — Python Control Flow

Control flow is how a Python program decides what to run and how many times: if/elif/else for branching, and for/while for repetition. The single most common bug in this area isn't syntax — it's confusing "no value" with "a falsy value." 0, "", and [] are all falsy in Python, exactly like None, so a plain if value: check can silently treat legitimate zero or empty values the same as missing ones.

0
a legitimate value that Python still treats as falsy
The source of the hook bug below
2
loop types: for (known sequence), while (until a condition changes)
Different tools for different jobs
is not None
the fix when a plain truthy check isn't specific enough
Covered in full below

The Zeros That Vanished

A discount-processing function needs to apply a discount only to rows where one was actually recorded. The intuitive check:

pythonTHE_BUG.PY
discounts = {"order_1": 10, "order_2": 0, "order_3": None}

for order_id, discount in discounts.items():
    if discount:
        print(f"{order_id}: applying {discount}% discount")
    else:
        print(f"{order_id}: no discount")

# order_2 has a real 0% discount recorded — but it prints "no discount",
# identical to order_3, which genuinely has no discount data at all

if discount: doesn't ask "was a discount recorded" — it asks "is this value truthy," and 0 fails that test exactly the same way None does. The bug is invisible in the output: both rows print the same "no discount" message, even though one of them is a real, meaningful zero. The fix is being explicit about what you're actually checking, covered fully in the next section.

if / elif / else: The Anatomy

Python's conditional structure reads close to plain English, and indentation — not braces — defines which code belongs to which branch.

pythonIF_ELIF_ELSE.PY
score = 72

if score >= 90:
    grade = "A"
elif score >= 80:
    grade = "B"
elif score >= 70:
    grade = "C"
else:
    grade = "F"

print(grade)  # "C"

Python checks each condition top to bottom and stops at the first one that's true — the rest are never evaluated. elif is Python's word for "else if"; there's no separate keyword for it, and a chain can have as many elif branches as needed before an optional final else.

Truthy and Falsy Values: Fixing the Hook Bug

Every value in Python has an implicit boolean identity, even outside an explicit comparison — this is what if discount: was actually testing.

Falsy valuesEverything else is truthy
NoneAny non-zero number
FalseAny non-empty string
0, 0.0Any non-empty list, dict, set, tuple
"", [], {}, ()
pythonTHE_FIX.PY
for order_id, discount in discounts.items():
    if discount is not None:   # checks specifically for missing data
        print(f"{order_id}: applying {discount}% discount")
    else:
        print(f"{order_id}: no discount data")

# now order_2 (discount=0) is correctly treated as "0% discount applied,"
# distinct from order_3, which genuinely has no data

is not None checks specifically for the absence of a value — it doesn't care whether the value happens to also be falsy for other reasons. This distinction matters anywhere zero, an empty string, or an empty list could be a legitimate, meaningful value rather than a stand-in for "missing."

for Loops: Iterating a Known Sequence

A for loop runs once per item in a sequence — a list, a string, a range of numbers, or a dictionary's keys.

pythonFOR_LOOPS.PY
fruits = ["apple", "banana", "cherry"]

for fruit in fruits:
    print(fruit)

# enumerate() gives you the index and the value together
for i, fruit in enumerate(fruits):
    print(i, fruit)

# range() for a fixed number of iterations
for i in range(5):
    print(i)  # 0, 1, 2, 3, 4

enumerate() replaces the manual pattern of tracking a separate counter variable and incrementing it by hand — reach for it any time both the position and the value are needed inside the loop body.

while Loops: Repeating Until a Condition Changes

A while loop keeps running as long as its condition stays true — the right tool when the number of iterations isn't known ahead of time.

pythonWHILE_LOOPS.PY
attempts = 0
max_attempts = 3

while attempts < max_attempts:
    print(f"Attempt {attempts + 1}")
    attempts += 1
⚠ The infinite loop trap

A while loop only stops when its condition becomes false. Forgetting to update the variable the condition depends on — like attempts += 1 above — produces an infinite loop that never terminates on its own.

break and continue

Both alter a loop's normal flow, but in different directions: one exits, the other skips.

pythonBREAK_CONTINUE.PY
numbers = [4, 7, 2, 9, 1]

for n in numbers:
    if n == 9:
        break       # stop the loop entirely
    if n % 2 == 0:
        continue    # skip this iteration, move to the next
    print(n)     # prints 7, then 1 (skips 4 and 2, stops before 9)

The Loop-Mutation Trap

The other classic control-flow bug: removing items from a list while iterating over that same list.

pythonMUTATION_TRAP.PY
numbers = [1, 2, 3, 4, 5]

# WRONG: removing while iterating skips elements
for n in numbers:
    if n % 2 == 0:
        numbers.remove(n)  # shifts remaining items, some get skipped

# CORRECT: build a new filtered list instead
numbers = [1, 2, 3, 4, 5]
numbers = [n for n in numbers if n % 2 != 0]

Removing an item shifts every element after it one position to the left — but the loop's internal position counter doesn't know that, so it skips the item that just slid into the spot it already passed. Building a new list (or looping over numbers.copy() instead of numbers directly) avoids the problem entirely.

Mistakes That Give Away a Beginner

  • Using if value: when 0 or "" could be legitimate — the exact trap from this article's opening. Use is not None when that distinction matters.
  • Removing items from a list while looping over it directly, silently skipping elements.
  • Forgetting to update a while loop's condition variable, causing an infinite loop.
  • Using break when continue was meant, exiting a loop entirely instead of just skipping one iteration.
  • Manually tracking an index with a counter variable instead of reaching for enumerate().

Practice: Fix These Three Yourself

1

Fix the falsy-zero bug

Rewrite the discount example so a real 0% discount is treated differently from missing discount data.

2

Filter without mutating

Given a list of numbers, build a new list containing only values greater than 10, without modifying the original list while looping.

3

Use enumerate()

Print each item in a list of names alongside its 1-based position (not 0-based) using enumerate().

Frequently Asked Questions

What is the difference between if x and if x is not None in Python?

if x checks whether x is truthy, which excludes not just None but also 0, empty strings, empty lists, and False. if x is not None checks specifically for the absence of a value, and still passes for 0, empty strings, or False. Using if x when you actually mean if x is not None is a common source of silently dropped valid data.

Why does Python treat 0 as False?

Python defines a set of falsy values that evaluate to False in a boolean context, including 0, 0.0, empty strings, empty collections, and None. This is a deliberate language design choice for convenience in many cases, but it means a plain if value: check cannot distinguish a legitimate zero from a missing value.

What is the difference between break and continue in Python?

break exits the loop entirely, skipping any remaining iterations. continue skips only the rest of the current iteration and moves on to the next one, without exiting the loop. Both only affect the nearest enclosing loop.

Why shouldn't you modify a list while looping over it in Python?

Removing or adding items to a list while iterating over it directly shifts the index positions of the remaining items mid-loop, which causes some elements to be silently skipped. The safe approaches are looping over a copy of the list, building a new filtered list instead, or iterating in reverse when removing by index.

What is the difference between a for loop and a while loop in Python?

A for loop iterates over a known sequence or a fixed number of times, such as every item in a list. A while loop repeats as long as a condition remains true, which is better suited when the number of iterations isn't known in advance, such as reading input until the user chooses to stop.

What does enumerate() do in Python?

enumerate() wraps an iterable and returns pairs of an automatic index and the corresponding value, so a for loop can access both the position and the item without manually tracking a separate counter variable.

Conclusion: Be Explicit About What You're Checking

Python's control flow syntax is simple; the bugs it hides are subtle. The one habit worth carrying forward from this article: a plain if value: check is a truthy test, not a "does this exist" test, and the two are only the same when zero, empty strings, and empty collections can never be legitimate values in your data — which, in real data science work, they very often are. Loop deliberately, never mutate what you're iterating over, and reach for is not None the moment that distinction actually matters.

Practice the three exercises above, then move to the next article in this cluster, which covers functions and scope — how to package this logic into reusable, well-behaved pieces.

🐍 Continue the Python Cluster

Next Up: Python Functions and Scope

Turning repeated logic into clean, reusable functions — and the scope rules that govern them.

▶  Read the Functions and Scope Guide