Search Tech Journey

Find topics, journeys and posts

6-month learning plan6 / 130
back to blog
pythonbeginner 15m read

S006 · Control Flow — if/else, loops, comprehensions

Every program is a giant decision tree.

Module M01: Python Foundations · Session 6 of 130 · Track: SW 🔁

What you'll be able to do after this session

  • Explain Control Flow to a friend in one minute, out loud, no notes.
  • Recognise it when you see it in real code / systems / papers.
  • Complete the hands-on exercise at the end without looking up help.

Prerequisites

If you can't answer these in 30 seconds each, redo the linked session first:


Pre-read (skim before the session)


(a) Intuition · 5 min

The one-paragraph mental model. Why this concept exists, what problem it solves, and the analogy that makes it stick.

In one sentence: Every program is a giant decision tree.

Every program that has ever run — from Excel to GPT — boils down to two ideas: do this, then that (sequence), and decide what to do next (control flow). Control flow is the branching + repeating structure that turns straight-line code into something that reacts to data.

Python gives you three primitives: if/elif/else (a decision), for (iterate over a known collection), and while (repeat until a condition changes). That's it. Everything more exotic — pattern matching, comprehensions, generators — is sugar on top.

The two-line insight that makes it click: for in Python is not a counter loop; it's a for-each loop over any iterable. You don't write for (i=0; i<n; i++); you write for user in users: — much more direct. When you do need an index, use enumerate(users). When you need to loop over two things in parallel, use zip(a, b).

Comprehensions ([x*2 for x in xs if x > 0]) are the "Python signature move" — they replace 4-line loops with one line that reads left-to-right like English. Master them and your code will look native.

Gotcha to carry forever: else on a for loop runs only if the loop finished without hitting break. This is one of Python's most surprising features and once you know it, you'll use it for search patterns. Also: never modify a list while iterating over it — either build a new list or iterate over a copy.


(b) Visual walkthrough · 15 min

Diagrams, worked examples, step-by-step visuals.

Control flow at a glance:

The decision table — which construct fits which problem:

You want to…UseSketch
Branch on a conditionif / elif / elseif x > 0: … elif x == 0: … else: …
Iterate a known collectionforfor user in users: …
Iterate + know the indexenumeratefor i, u in enumerate(users): …
Iterate two lists in lockstepzipfor a, b in zip(xs, ys): …
Repeat until a condition changeswhilewhile not done: …
Build a new list from an old onelist comprehension[f(x) for x in xs if pred(x)]
Build a dictdict comprehension{k: v for k, v in pairs}
Deduplicateset comprehension{f(x) for x in xs}
Lazy sequence (memory-cheap)generator expression(f(x) for x in xs)
Multi-way branch on shapematch (Py 3.10+)match cmd: case "go": …

Worked example 1 — the FizzBuzz classic (bad → good):

# Beginner version
for i in range(1, 16):
    if i % 15 == 0:
        print("FizzBuzz")
    elif i % 3 == 0:
        print("Fizz")
    elif i % 5 == 0:
        print("Buzz")
    else:
        print(i)
 
# Pythonic one-liner using comprehension + conditional expression
print("\n".join(
    "FizzBuzz" if i % 15 == 0 else "Fizz" if i % 3 == 0 else "Buzz" if i % 5 == 0 else str(i)
    for i in range(1, 16)
))

Worked example 2 — search with for/else:

users = [{"name": "alice"}, {"name": "bob"}, {"name": "carol"}]
target = "dave"
 
for u in users:
    if u["name"] == target:
        print("found", u)
        break
else:
    print(target, "not in list")   # runs only if the loop completed with no break

for/else is the cleanest way to express "search a collection; do X if not found" without a found = False flag.


(c) Hands-on · 20 min

Code you type and run. Not pseudo-code — real, runnable, copy-pasteable.

Ten miniature exercises — type each one and read the output. This should take ~20 minutes.

# s006_control_flow.py
 
# 1. if / elif / else
def grade(pct: int) -> str:
    if pct >= 90:  return "A"
    elif pct >= 75: return "B"
    elif pct >= 50: return "C"
    else:            return "F"
 
for p in (95, 80, 60, 30):
    print(p, "->", grade(p))
 
# 2. for over a list
fruits = ["apple", "banana", "cherry"]
for f in fruits:
    print("i like", f)
 
# 3. enumerate for index + value
for i, f in enumerate(fruits, start=1):
    print(f"{i}. {f}")
 
# 4. zip for parallel iteration
prices = [50, 20, 300]
for f, p in zip(fruits, prices):
    print(f"{f}: ₹{p}")
 
# 5. while with a real exit condition
import random
random.seed(42)
rolls = 0
while (r := random.randint(1, 6)) != 6:
    rolls += 1
print(f"rolled {rolls} times before getting a 6")
 
# 6. list comprehension replacing a loop
squares = [x * x for x in range(10)]
print(squares)
 
# 7. filtering with a comprehension
evens = [x for x in range(20) if x % 2 == 0]
print(evens)
 
# 8. dict + set comprehensions
name_len = {f: len(f) for f in fruits}
print(name_len)
first_letters = {f[0] for f in fruits}
print(first_letters)
 
# 9. for/else — search that reports "not found"
def find_user(users, target):
    for u in users:
        if u == target:
            print("found", target)
            break
    else:
        print(target, "not found")
 
find_user(["alice", "bob"], "bob")
find_user(["alice", "bob"], "carol")
 
# 10. match/case (Python 3.10+)
def http_status(code: int) -> str:
    match code:
        case 200: return "OK"
        case 301 | 302: return "redirect"
        case n if 400 <= n < 500: return "client error"
        case n if 500 <= n < 600: return "server error"
        case _: return "unknown"
 
for c in (200, 302, 404, 500, 999):
    print(c, http_status(c))

Checklist — what to observe:

  1. Exercise 1: no switch in Python — elif chains are idiomatic.
  2. Exercise 5 uses the walrus operator := (Py 3.8+) to assign inside the while. This is a common test-then-loop pattern.
  3. Exercise 6's comprehension is faster than the equivalent for + append loop in CPython.
  4. Exercise 9 shows for/else: the "not found" branch fires only when no break happened.
  5. Exercise 10's match uses guards (case n if …) and OR patterns (301 | 302).

Try this modification: rewrite Exercise 1 with a match statement using range guards. Then convert Exercise 6 into a generator expression ((x*x for x in range(10))) and observe what changes when you print it (spoiler: you see a generator object; wrap it in list() to materialise).


(d) Production reality · 10 min

How this shows up in real systems, gotchas, war stories, common bugs.

War story — the O(n²) list-in-a-loop. A data pipeline at a hedge fund did if x not in seen: seen.append(x) inside a loop over 500 000 items. in on a list is O(n), so the pipeline was O(n²) — 40 minutes to run. Changing seen from list to set (which has O(1) in) dropped it to 2 seconds. Rule: whenever you use in, ask "is this a set or dict?" If not, it might need to be.

War story — the modified-while-iterating crash. A dev at a startup wrote for u in users: if u.expired: users.remove(u). Python silently skipped every other user; some expired users survived. Correct patterns: iterate a copy (for u in users[:]:), build a new list (users = [u for u in users if not u.expired]), or iterate indices in reverse (for i in range(len(users)-1, -1, -1): …).

War story — the infinite loop in prod. A while queue: process(queue.pop()) looked innocent, but process sometimes added to the queue. Under load, the queue grew faster than it shrank — infinite loop, 100 % CPU, pager storm at 2 a.m. Rule: every while needs a provable termination argument (a decreasing counter, a bounded external condition, a timeout). Prefer for _ in range(MAX_ITERATIONS): with an inner condition when you can.

How top teams write control flow:

  • Early returns > deep nesting. Guard clauses (if not user: return None) beat 4-level if pyramids. Google's style guide explicitly recommends this.
  • Comprehensions for transforms; loops for side effects. A comprehension that prints is a code smell.
  • match/case for state machines and parsers. Not for every 3-way branch — if/elif is often clearer.
  • Prefer any() / all() over hand-rolled search loops. if any(u.expired for u in users) is one line and short-circuits.
  • Type-hint the collection. def process(users: list[User]) -> list[User]: makes the loop's contract obvious.

One counter-intuitive perf note: in tight numerical loops, a comprehension [f(x) for x in xs] is usually faster than map(f, xs) in CPython 3.11+, and both are slower than NumPy vectorisation. If a loop is your bottleneck, the answer is often "stop looping — vectorise."


(e) Quiz + exercise · 10 min

  1. What's the difference between a for loop and a while loop, and when do you prefer each?
  2. What does the else clause on a for loop do, and when does it run?
  3. Rewrite result = []; for x in xs: if x > 0: result.append(x*2) as a single comprehension.
  4. Why is if item in my_list sometimes O(n) and sometimes O(1)? What's the fix?
  5. Why is modifying a list while iterating over it a bug, and what are two safe alternatives?

Stretch problem (connects to S005): given logs = ["INFO ok", "ERROR db", "WARN slow", "ERROR net", "INFO ok"], use a single dict-comprehension + collections.Counter (or a plain loop) to produce {"INFO": 2, "ERROR": 2, "WARN": 1}. Then explain in one sentence why using a dict here beats using a list of (level, count) tuples.


Explain-out-loud test

If you can't teach these 3 things to a friend without notes, redo the session:

  1. What is Control Flow? (one sentence, no jargon)
  2. When would you use it? (one concrete example)
  3. When would you NOT use it? (one anti-pattern)

What comes next


Part of a 130-session evergreen learning series. Session structure: (a) intuition · (b) visual walkthrough · (c) hands-on · (d) production reality · (e) quiz + exercise. Duration: 60 minutes.

More from M01 · Python Foundations

all modules →
  1. 005Python Variables & Types — Mental Model of Memory
  2. 007Functions — arguments, scope, closures
  3. 008Data Structures — list, tuple, dict, set (when to use what)
  4. 009Classes & Objects — the OOP Mental Model
  5. 010Inheritance, Composition & Polymorphism
  6. 011Errors, Exceptions & Debugging with pdb