Conditionals: Making Decisions
Programs need to make decisions. Should we show an error message? Is the user logged in? Is this number positive? Python uses if, elif (short for "else if"), and else to handle these decisions. Unlike languages that use curly braces, Python uses indentation to define which code belongs to which condition.
if / elif / else
The basic pattern is: check a condition, and if it's true, run the indented code block. You can chain multiple conditions with elif, and provide a fallback with else:
age = 20
if age < 13:
print("Child")
elif age < 18:
print("Teenager")
elif age < 65:
print("Adult")
else:
print("Senior")
Adult
Python evaluates conditions top to bottom and runs the first block that matches. Once a match is found, it skips all remaining elif and else blocks. This means order matters — put your most specific conditions first.
Indentation is Syntax
Python uses 4 spaces of indentation to define code blocks. This isn't just a style choice — it's part of the language. Mixing tabs and spaces will cause errors. Configure your editor to insert 4 spaces when you press Tab.
Truthy and Falsy Values
In Python, every value has a boolean interpretation. You don't always need explicit comparisons like if x == True or if len(items) > 0. Python considers these values falsy (they evaluate to False):
False,0,0.0— zero values""— empty string[],(),{},set()— empty collectionsNone— Python's null
Everything else is truthy. This lets you write clean, Pythonic conditions:
# Instead of: if len(name) > 0:
name = ""
if name:
print(f"Hello, {name}")
else:
print("Name is empty")
# Instead of: if len(items) > 0:
items = [1, 2, 3]
if items:
print(f"Got {len(items)} items")
Name is empty Got 3 items
if items: is more Pythonic than if len(items) > 0:.Ternary Expression (Conditional Expression)
For simple if/else assignments, Python has a one-liner syntax. It reads almost like English: "value if condition else other_value":
age = 20
status = "adult" if age >= 18 else "minor"
print(status)
# Useful in f-strings too
count = 1
print(f"Found {count} item{'s' if count != 1 else ''}")
adult Found 1 item
For Loops: Iterating Over Data
The for loop is Python's primary iteration tool. Unlike C-style for loops that use counters (for(i=0; i
Iterating Over Sequences
# Iterate over a list
fruits = ["apple", "banana", "cherry"]
for fruit in fruits:
print(fruit)
print("---")
# Iterate over a string (character by character)
for char in "Python":
print(char, end=" ")
apple banana cherry --- P y t h o n
enumerate() — When You Need the Index
Sometimes you need both the item AND its position. Don't use range(len(list)) — use enumerate() instead. It's cleaner and avoids off-by-one errors:
fruits = ["apple", "banana", "cherry"]
# Bad: C-style indexing
# for i in range(len(fruits)):
# print(i, fruits[i])
# Good: Pythonic
for i, fruit in enumerate(fruits):
print(f"{i}: {fruit}")
# Start counting from 1
for i, fruit in enumerate(fruits, start=1):
print(f"#{i} {fruit}")
0: apple 1: banana 2: cherry #1 apple #2 banana #3 cherry
range() — Generating Number Sequences
range() generates a sequence of numbers. It's lazy — it doesn't create a list in memory, it generates numbers on demand. This makes it efficient even for huge ranges:
# range(stop) - 0 to stop-1
for i in range(5):
print(i, end=" ") # 0 1 2 3 4
print()
# range(start, stop) - start to stop-1
for i in range(2, 6):
print(i, end=" ") # 2 3 4 5
print()
# range(start, stop, step) - with step size
for i in range(0, 20, 5):
print(i, end=" ") # 0 5 10 15
print()
# Counting backwards
for i in range(5, 0, -1):
print(i, end=" ") # 5 4 3 2 1
0 1 2 3 4 2 3 4 5 0 5 10 15 5 4 3 2 1
While Loops
A while loop repeats as long as a condition is true. Use it when you don't know in advance how many iterations you need — like reading user input until they type "quit", or processing data until a queue is empty:
# Countdown
count = 5
while count > 0:
print(count, end=" ")
count -= 1
print("Go!")
5 4 3 2 1 Go!
break and continue
break exits the loop entirely. continue skips the rest of the current iteration and moves to the next one. Think of break as "I found what I need, stop looking" and continue as "skip this one, try the next":
# break - exit the loop early
for i in range(10):
if i == 5:
print(f"Found 5! Stopping.")
break
print(i, end=" ")
print()
# continue - skip certain iterations
for i in range(10):
if i % 3 == 0:
continue # Skip multiples of 3
print(i, end=" ")
0 1 2 3 4 Found 5! Stopping. 1 2 4 5 7 8
🔍 Deep Dive: The else Clause on Loops
Python loops can have an else clause that runs only if the loop completes without hitting break. This is useful for search patterns: if you find what you're looking for, break; otherwise the else block handles the "not found" case. Example: for item in items: if match(item): break followed by else: print("not found"). It's a niche feature but very elegant when you need it.
Loop Patterns
Python has several elegant patterns for common iteration tasks. Learning these will make your code shorter, clearer, and more Pythonic.
Looping Over Dictionaries
Dictionaries support three iteration styles. By default, iterating gives you keys. Use .items() for key-value pairs, .values() for just values:
scores = {"Alice": 95, "Bob": 87, "Charlie": 92}
# Key-value pairs (most common)
for name, score in scores.items():
print(f"{name}: {score}")
print("---")
# Just keys (default)
for name in scores:
print(name)
# Just values
total = sum(scores.values())
print(f"Average: {total / len(scores):.1f}")
Alice: 95 Bob: 87 Charlie: 92 --- Alice Bob Charlie Average: 91.3
zip() — Parallel Iteration
Need to iterate over two (or more) sequences at the same time? zip() pairs them up element by element. It stops at the shortest sequence:
names = ["Alice", "Bob", "Charlie"]
scores = [95, 87, 92]
grades = ["A", "B+", "A-"]
for name, score, grade in zip(names, scores, grades):
print(f"{name}: {score} ({grade})")
Alice: 95 (A) Bob: 87 (B+) Charlie: 92 (A-)
enumerate() for index+value, zip() for parallel iteration, and .items() for dictionaries. Avoid C-style range(len(...)) indexing.Match/Case (Python 3.10+)
Python 3.10 introduced structural pattern matching — similar to switch/case in other languages, but much more powerful. It can match values, types, and even destructure data:
command = "quit"
match command:
case "start":
print("Starting...")
case "stop" | "quit" | "exit":
print("Stopping...")
case str() as cmd if cmd.startswith("set "):
_, value = cmd.split(" ", 1)
print(f"Setting: {value}")
case _:
print(f"Unknown command: {command}")
Stopping...
The _ wildcard matches anything — it's the default case. The | operator matches any of several values. You can also add if guards for additional conditions.
⚠️ Common Mistake: Modifying a List While Iterating
Wrong:
items = [1, 2, 3, 4, 5]
for item in items:
if item % 2 == 0:
items.remove(item) # Bug! Skips elements
print(items) # [1, 3, 5]? No! [1, 3, 5] sometimes, [1, 3, 4] other times
Why: Removing items shifts indices, causing the loop to skip elements. The behavior is unpredictable.
Instead: Use a list comprehension to create a new filtered list:
# Correct: create a new list
items = [1, 2, 3, 4, 5]
items = [x for x in items if x % 2 != 0]
print(items) # [1, 3, 5] — always correct
Practice Exercises
FizzBuzz
Print numbers 1-20. For multiples of 3, print "Fizz". For 5, print "Buzz". For both, print "FizzBuzz".
Sum of Digits
Write a while loop that sums the digits of a number. E.g., 1234 → 1+2+3+4 = 10.
Find the Max
Use a for loop to find the largest number in a list without using the built-in max().