Control Flow in Python
Master if/elif/else, match-case, for and while loops, break/continue, and list comprehensions.
if / elif / else
Conditional statements let your program make decisions based on runtime values. Python uses indentation (4 spaces by convention) to delimit blocks instead of braces, and there are no parentheses required around the condition.
score = 85
if score >= 90:
grade = "A"
elif score >= 80:
grade = "B" # this branch runs because 85 >= 80
elif score >= 70:
grade = "C"
else:
grade = "F"
print(grade) # B
Inline Conditional (Ternary)
For simple two-branch conditions, Python’s ternary expression keeps the logic on one readable line. Reserve it for simple cases — nesting ternaries hurts readability fast.
# Returns "adult" or "minor" depending on age — no if block needed
status = "adult" if age >= 18 else "minor"
Truthiness Shortcuts
Python evaluates any object in a boolean context, not just explicit True/False. Leveraging this makes conditions more concise and idiomatic — the second form below is considered un-Pythonic.
items = []
# Idiomatic — empty list is falsy
if not items:
print("Empty")
# Verbose — works but unnecessary
if len(items) == 0:
print("Empty")
match-case (Python 3.10+)
match-case is structural pattern matching — a significant upgrade over a traditional value-switch. It can destructure sequences, match object attributes, and bind variables in a single step, which eliminates the nested if/elif chains that were previously needed for command parsing or protocol handling.
def handle_command(command):
match command.split():
case ["quit"]:
return "Exiting."
case ["go", direction]:
# 'direction' is bound to the second word automatically
return f"Going {direction}"
case ["pick", "up", item]:
return f"Picked up {item}"
case ["drop", *items]:
# *items captures all remaining words into a list
return f"Dropped: {', '.join(items)}"
case _:
# _ is the wildcard — matches anything not caught above
return f"Unknown command: {command}"
handle_command("go north") # "Going north"
handle_command("pick up sword") # "Picked up sword"
handle_command("drop key map") # "Dropped: key, map"
Matching on Types and Attributes
match-case can match against class types and bind specific attribute values in the same case clause — something that would require multiple isinstance calls and attribute accesses with traditional if/elif.
from dataclasses import dataclass
@dataclass
class Point:
x: float
y: float
def classify(point):
match point:
case Point(x=0, y=0):
return "Origin"
case Point(x=0, y=y):
return f"Y-axis at {y}"
case Point(x=x, y=0):
return f"X-axis at {x}"
case Point(x=x, y=y):
return f"Point at ({x}, {y})"
classify(Point(0, 5)) # "Y-axis at 5"
for Loops
Python’s for loop iterates over any iterable — lists, strings, ranges, generators, files, dictionaries, and more. There is no C-style for (i=0; i<n; i++) in Python; if you need an index, use enumerate().
fruits = ["apple", "banana", "cherry"]
for fruit in fruits:
print(fruit)
# range() generates integers on demand — no list is created in memory
for i in range(5): # 0, 1, 2, 3, 4
print(i)
for i in range(2, 10, 2): # 2, 4, 6, 8 (start, stop, step)
print(i)
enumerate() and zip()
enumerate() and zip() solve two of the most common looping patterns — getting an index alongside the value, and iterating two sequences together — without manually managing index variables.
# enumerate() — get index and value together; avoids error-prone manual indexing
for i, fruit in enumerate(fruits, start=1):
print(f"{i}. {fruit}")
# 1. apple
# 2. banana
# 3. cherry
# zip() — iterate two sequences in lockstep
names = ["Alice", "Bob"]
scores = [95, 87]
for name, score in zip(names, scores):
print(f"{name}: {score}")
for / else
The else clause on a for loop runs when the loop completes normally — that is, without hitting a break. This is useful for “search and report not found” patterns without needing a sentinel variable.
def find_prime(numbers):
for n in numbers:
for i in range(2, int(n**0.5) + 1):
if n % i == 0:
break # composite — inner loop exits early
else:
# else runs only when the inner loop finished without break
return n # found a prime
return None
while Loops
while loops repeat as long as their condition is truthy. They’re the right tool when you don’t know in advance how many iterations you need — waiting for user input, reading until EOF, or polling until a condition is met.
count = 0
while count < 5:
print(count)
count += 1
# Infinite loop — use break to exit when a condition is met
while True:
user_input = input("Enter 'quit' to exit: ")
if user_input == "quit":
break
print(f"You said: {user_input}")
Walrus Operator in while
The walrus operator (:=) is particularly useful in while loops — it reads a value, assigns it, and checks it in a single expression, eliminating the boilerplate of reading before the loop and again at the top of each iteration.
import sys
# Read lines until EOF — assign and check in one expression
while line := sys.stdin.readline():
process(line.strip())
break and continue
break and continue give you fine-grained control over loop execution. They’re most useful when a condition inside the loop body is easier to express than rewriting the loop condition itself.
# break — exits the entire loop immediately
for n in range(10):
if n == 5:
break
print(n) # 0, 1, 2, 3, 4
# continue — skips the rest of this iteration, moves to the next
for n in range(10):
if n % 2 == 0:
continue # skip even numbers
print(n) # 1, 3, 5, 7, 9
Comprehensions
Comprehensions provide a concise syntax for constructing new collections from existing iterables. They’re not just syntactic sugar — Python compiles them to optimized bytecode that’s typically faster than equivalent for loops with .append() calls.
List Comprehension
# Basic transformation — square every number from 0 to 9
squares = [x**2 for x in range(10)]
# With filter — only include even numbers
evens = [x for x in range(20) if x % 2 == 0]
# Nested — flatten a 2D matrix into a 1D list
matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
flat = [cell for row in matrix for cell in row]
# [1, 2, 3, 4, 5, 6, 7, 8, 9]
Dict Comprehension
Dict comprehensions create dictionaries with the same concise syntax, useful for transforming or inverting mappings without verbose loop boilerplate.
words = ["hello", "world", "python"]
# Map each word to its length
word_lengths = {word: len(word) for word in words}
# {"hello": 5, "world": 5, "python": 6}
# Invert a dict — swap keys and values
original = {"a": 1, "b": 2, "c": 3}
inverted = {v: k for k, v in original.items()}
# {1: "a", 2: "b", 3: "c"}
Set Comprehension
Set comprehensions automatically deduplicate results, making them useful when you want a unique collection of transformed values.
numbers = [1, 2, 2, 3, 3, 3, 4]
# {1, 4, 9, 16} — duplicates from the input are collapsed
unique_squares = {x**2 for x in numbers}
Generator Expression
A generator expression looks like a list comprehension but produces values lazily — one at a time, on demand. Use it when you only need to iterate once or when the full collection would use too much memory.
# List comprehension — builds the entire list in memory (1M integers)
total = sum([x**2 for x in range(1_000_000)])
# Generator expression — streams values one at a time, no list created
total = sum(x**2 for x in range(1_000_000)) # no square brackets
Common Patterns
Flat map
sentences = ["hello world", "foo bar baz"]
# Split each sentence into words and flatten into one list
words = [word for sentence in sentences for word in sentence.split()]
# ["hello", "world", "foo", "bar", "baz"]
Conditional expression in comprehension
values = [1, -2, 3, -4, 5]
# Take absolute value of each element inline — no math.fabs needed
abs_values = [x if x >= 0 else -x for x in values]
# [1, 2, 3, 4, 5]
Early exit with next()
next() with a generator expression efficiently finds the first match in a sequence without scanning the whole thing. The second argument is the default if nothing matches.
users = [{"id": 1, "name": "Alice"}, {"id": 2, "name": "Bob"}]
# Stops at the first match — doesn't scan the rest of the list
alice = next((u for u in users if u["name"] == "Alice"), None)