Python Control Flow


  • Description: if/elif/else, conditional expressions, for/while loops, the else clause on loops, match/case pattern matching, and the walrus operator :=
  • My Notion Note ID: K2A-D1-7
  • Created: 2022-07-02
  • Updated: 2026-05-11
  • License: Reuse is very welcome. Please credit Yu Zhang and link back to the original on yuzhang.io

Table of Contents


1. if / elif / else

if x > 0:
    sign = 1
elif x < 0:
    sign = -1
else:
    sign = 0
  • No C-style switch before 3.10, elif chains take its place
  • Since 3.10, match/case provides a more powerful alternative

1.1 Conditional Expression

status = "even" if n % 2 == 0 else "odd"
y = a / b if b != 0 else 0
  • Order is value_if_true if cond else value_if_false, condition in the middle (unlike C++ cond ? a : b)
  • Use only for short, side-effect-free expressions

2. Loops

2.1 for-in

  • Always over an iterable, no C-style for (int i = 0; ...)
  • To get indices: use range or enumerate
for x in xs:                    # value iteration
    ...

for i in range(len(xs)):        # index iteration (rarely the right answer)
    ...

for i, x in enumerate(xs):      # both, preferred
    ...

for a, b in zip(xs, ys):
    ...
  • Modifying a list while iterating over it is undefined, iterate over a copy (xs[:]) or build a new list

2.2 while

while not done:
    done = step()
  • while True: with break is common and idiomatic
  • No do { ... } while ();

2.3 break, continue, pass

  • break, exit innermost loop
  • continue, next iteration
  • pass, do nothing; placeholder where a statement is required
if x < 0:
    pass     # TODO: handle later
else:
    process(x)

class TODO:
    pass     # empty class body
  • No labeled break, refactor into a function and return, or use a flag

2.4 else on a Loop

  • for/while may have an else block that runs if the loop completed WITHOUT hitting break
  • Read as nobreak
for n in candidates:
    if is_prime(n):
        first_prime = n
        break
else:
    raise ValueError("no prime in candidates")
  • Wrap in a function and return if the construct hurts readability

3. match / case (Structural Pattern Matching)

PEP 634 (3.10+). Destructures tuples, lists, dicts, and classes:

def handle(event):
    match event:
        case ("click", x, y):
            click(x, y)
        case ("key", key) if key in modifiers:
            modifier_press(key)
        case {"type": "scroll", "dy": dy}:
            scroll(dy)
        case Point(x=0, y=0):
            origin()
        case [head, *tail]:
            recur(head, tail)
        case _:
            unknown(event)
  • Patterns matched top-to-bottom
  • _ = wildcard
  • case Cls(field=...) matches an instance and binds fields (needs __match_args__ or named fields; @dataclass provides automatically)
  • case ... if cond: adds a guard
  • Captures use bare names: case x: BINDS x. To match a constant, use a dotted name (case Color.RED:) or quote (case "red":)

4. The Walrus Operator :=

PEP 572 (3.8+), "assignment expression". Assign and use a value in one expression.

while (line := f.readline()):
    process(line)

if (n := len(buffer)) > MAX:
    raise ValueError(f"buffer too large: {n}")

[y for x in data if (y := transform(x)) is not None]
  • Use sparingly, only for genuine readability gains
  • Not allowed at the top level of an expression statement (x := 1 alone is a syntax error)