Week 2: Control Flow
🎯 Learning objectives
By the end of this week you can:
- Branch a program's behavior with
if/elif/else. - Combine conditions with
and,or, andnot. - Repeat work with
whileandforloops, and choose the right one. - Use
range()to loop a fixed number of times, andbreak/continueto control a loop early. - Nest loops and conditionals to solve problems with more than one moving part.
Lesson
Branching: if / elif / else
An if statement is a piecewise definition. Compare:
x = -3
if x < 0:
print("negative")
elif x == 0:
print("zero")
else:
print("positive")
Indentation is not style here — it is the syntax. Everything indented under if belongs to that branch; Python has no { } to mark blocks. A block needs at least one statement — if you want a branch that deliberately does nothing (e.g. while sketching out a program's shape before filling it in), use pass, a statement whose only job is to do nothing:
if x < 0:
pass # TODO: handle negative numbers later
else:
print("non-negative")
Combining conditions: and, or, not
Just like , , in logic, Python has and, or, not for combining boolean expressions:
age = 20
has_ticket = True
if age >= 18 and has_ticket:
print("Welcome in")
if age < 13 or age > 65:
print("Discount applies")
if not has_ticket:
print("Buy a ticket first")
They follow the same truth tables you'd expect: and is true only when both sides are true; or is true when at least one side is true; not flips a boolean. Python also short-circuits: in a and b, if a is already False, Python never even evaluates b, since the whole expression must be False regardless. This matters when b might be expensive or unsafe to evaluate — e.g. x != 0 and 10 / x > 1 never risks dividing by zero, because if x != 0 is False, the division is skipped entirely.
while: repeat until a condition fails
A while loop is the closest thing to a mathematical recurrence with a stopping condition:
n = 5
while n > 0:
print(n)
n = n - 1
print("liftoff!")
This prints 5 4 3 2 1 liftoff!. The loop re-checks n > 0 before every iteration — if you forget to update n inside the loop, it never becomes false, and you get an infinite loop.
while is the right tool when you don't know in advance how many iterations you'll need — the stopping condition depends on something that happens during the loop, like user input or a search that could finish early:
guess = None
target = 42
while guess != target:
guess = int(input("Guess the number: "))
print("Correct!")
for: repeat over a known sequence
A for loop walks through a sequence of values directly — think of it as where is whatever you're iterating over, except you're running code for each instead of adding numbers:
for letter in "cat":
print(letter) # c, a, t — one per line
range(n) produces the sequence — exactly the index set you'd use for :
total = 0
for i in range(5):
total = total + i
print(total) # 0+1+2+3+4 = 10
range(start, stop) and range(start, stop, step) generalize this, mirroring an arithmetic sequence :
for i in range(2, 10, 2):
print(i) # 2, 4, 6, 8 — start at 2, stop before 10, step by 2
A negative step counts down: range(5, 0, -1) produces 5, 4, 3, 2, 1, the same sequence Week 1's while liftoff example built by hand.
break and continue
break exits a loop immediately; continue skips to the next iteration without finishing the current one:
for i in range(10):
if i == 5:
break # stop entirely once i reaches 5
if i % 2 == 0:
continue # skip printing even numbers
print(i) # prints 1, 3
Nesting: loops and conditionals inside each other
A loop body — or an if branch — can contain another loop or if, exactly the way a piecewise function's cases can themselves involve further conditions. Each level of nesting adds one more level of indentation:
for row in range(1, 4):
for col in range(1, 4):
print(row * col, end=" ") # end=" " prints a space instead of a newline
print() # move to the next line after each row
This prints a small 3×3 multiplication table:
1 2 3
2 4 6
3 6 9
The inner loop runs to completion for every iteration of the outer loop — for 3 outer iterations and 3 inner iterations each, that's total print statements, the same counting principle behind a double sum .
⚠️ Common pitfalls
- Off-by-one errors with
range.range(1, 10)stops before 10, giving1..9. If you want to include 10, you needrange(1, 11). - Forgetting to update the loop variable in a
while.while n > 0: print(n)with non -= 1inside never terminates. - Using
=instead of==in a condition. Python actually catches this one for you (if x = 5:is aSyntaxError), unlike some other languages — but it's still worth knowing why it would be a bug if it were allowed. - Assuming
elifchains check every branch. Once oneelif/ifcondition matches, the rest are skipped entirely — order matters, especially for overlapping conditions like FizzBuzz's "divisible by both 3 and 5" case.
🧩 Challenges
Write "FizzBuzz" for the numbers 1 through 10: print "Fizz" if divisible by 3, "Buzz" if divisible by 5, "FizzBuzz" if divisible by both, otherwise the number itself.
Write a loop that keeps asking "Type 'quit' to stop: " until the student types exactly quit. Would for or while fit this task better, and why?
Sum the integers from 1 to 100 using a loop, and check your program's answer against the closed-form formula .
Write a program that reads a number n and prints whether it's prime, by checking whether any integer from 2 up to (but not including) n divides it evenly.
Using nested loops, print a square of asterisks, n rows by n columns, for a number n you choose (e.g. a 4×4 block of * characters).
Write a condition using and that checks whether a variable age is between 18 and 65 (inclusive of 18, exclusive of 65) and a boolean has_ticket is True.
🤔 Socratic Questions
- What happens if you write
while True:with nobreakinside? Try it carefully in the playground (you may need to stop execution manually) — why is this different from aforloop over a fixedrange? - In FizzBuzz, why does the divisible-by-both case (
FizzBuzz) need to be checked before the separateFizz/Buzzchecks, or handled withand? What would go wrong with a naiveelifordering? for i in range(5):andi = 0; while i < 5: ...; i += 1do the same thing. Under what circumstances would you have to usewhilebecauseforgenuinely can't express it?- Short-circuit evaluation means
a and bskips evaluatingbifais alreadyFalse. Can you think of a situation (beyond the division-by-zero example above) where this matters for more than just performance — where evaluatingbwhen it shouldn't be evaluated would actually be wrong, not just wasteful? - The multiplication-table example nests a
forinside afor. What would nesting awhileinside aforlook like, and can you think of a real task (not just a made-up example) where that combination would be the natural choice?