Control Flow
A redstone circuit in Minecraft is a perfect machine. Power in, signal travels through repeaters, comparators, AND gates, OR gates, finally a piston fires or a door opens. Same input, same path, every time. Control flow is the redstone of Python: the wires that decide which line runs next, which lines repeat, and which lines never run at all. Up to this lesson your programs have been one straight wire from top to bottom. Now they branch.
In March 1968 a Dutch computer scientist named Edsger Dijkstra published a 2-page letter in the Communications of the ACM with the title "Go To Statement Considered Harmful." The goto statement let a programmer jump from any line to any other line in the program. Dijkstra argued that a program with arbitrary jumps was impossible to reason about — you could never look at line 47 and know how you got there. He proposed building all logic out of three primitives: sequence (do this, then this), selection (if this then that), and iteration (do this until that). Every modern language banned goto or hid it behind safer doors. Python never had one. The three primitives Dijkstra named are exactly the control-flow tools you are about to learn.

The first redstone gate is the if statement. It runs a block of code only when a condition is true. The condition is anything that evaluates to a Boolean — a comparison, a function call that returns True or False, or any value Python can interpret as truthy. Open Python with the python command and type this.
score = 87
if score >= 90:
print("A")
elif score >= 80:
print("B")
elif score >= 70:
print("C")
else:
print("fail")Output: B. Python checks each condition in order, runs the first block whose condition is true, and skips the rest. The elif is "else if" — if the first check failed, try this one. The else at the end is the catch-all for everything that did not match.
Python has a quiet rule about what counts as true. Anything that is not zero, not empty, and not None is truthy. The number 0, the empty string "", the empty list [], the empty dict {}, and None are all falsy. Everything else is truthy. That means you can write if name: instead of if name != "": to mean "name is not empty." The reader who knows the rule reads it instantly.
The Boolean operators and, or, and not combine conditions. They follow the truth table any electrician would draw for two switches and a bulb. and is true only when both sides are true. or is true when either side is true. not flips a Boolean. Try this in the prompt.
a = True
b = False
print("a and b:", a and b)
print("a or b:", a or b)
print("not a:", not a)
print("a and not b:", a and not b)a and b: False
a or b: True
not a: False
a and not b: TrueThe next primitive is the loop. A while loop runs a block over and over while a condition is true. The classic trace example is a counter — print the value of i at every iteration so you watch it move.
i = 0
while i < 5:
print("i is", i)
i += 1
print("done, i is", i)i is 0
i is 1
i is 2
i is 3
i is 4
done, i is 5A for loop walks every item in a collection. It is the right tool when you know what you are walking over and you want every element. The range function gives you a sequence of integers; range(5) is 0, 1, 2, 3, 4.
for letter in "ARK":
print(letter)
for n in range(3):
print("step", n)A
R
K
step 0
step 1
step 2Two keywords change the flow inside a loop. break exits the loop right now, no matter what the condition says. continue skips the rest of this iteration and jumps back to the top for the next one. Trace both at once.
for n in range(10):
if n == 3:
continue
if n == 7:
break
print("n is", n)
print("loop ended")n is 0
n is 1
n is 2
n is 4
n is 5
n is 6
loop endedThe 3 was skipped because continue jumped back to the top before the print. The 7 never printed because break exited the loop entirely. Numbers 8 and 9 never even got considered.

The right test of all three primitives is FizzBuzz, the question every junior programmer gets asked at every interview. The rules: print the numbers 1 through 15. If a number is divisible by 3, print "Fizz" instead. If divisible by 5, print "Buzz." If divisible by both, print "FizzBuzz." Write it as a for loop first.
for n in range(1, 16):
if n % 15 == 0:
print("FizzBuzz")
elif n % 3 == 0:
print("Fizz")
elif n % 5 == 0:
print("Buzz")
else:
print(n)1
2
Fizz
4
Buzz
Fizz
7
8
Fizz
Buzz
11
Fizz
13
14
FizzBuzzThe % is the modulo operator — it returns the remainder of dividing the left side by the right. 15 % 3 is 0 because 15 divides evenly. 7 % 3 is 1 because 7 = 2*3 + 1. The order of the if/elif chain matters: check the FizzBuzz case first, because 15 is divisible by 3 and 5, and if you check n % 3 == 0 before n % 15 == 0 you will print "Fizz" and never reach the FizzBuzz line.
Now the same logic as a while. Same output, different shape.
n = 1
while n <= 15:
if n % 15 == 0:
print("FizzBuzz")
elif n % 3 == 0:
print("Fizz")
elif n % 5 == 0:
print("Buzz")
else:
print(n)
n += 1In Python 3.10 from October 2021 a fourth tool arrived: match. It is pattern matching, similar to a switch statement in other languages but stricter and more powerful. For FizzBuzz it shines.
for n in range(1, 16):
match (n % 3, n % 5):
case (0, 0):
print("FizzBuzz")
case (0, _):
print("Fizz")
case (_, 0):
print("Buzz")
case _:
print(n)The match evaluates the tuple (n % 3, n % 5) once and then checks each case. The underscore _ means "match anything in this position." (0, 0) matches when both remainders are zero. (0, _) matches when the first remainder is zero and the second is anything. The final case _ is the catch-all. Same answer, more declarative shape.
Your code can branch and repeat now. The next bottleneck is that every program above is one long block — if you wanted FizzBuzz from 1 to 100 in three different places, you would copy the loop three times. You need a way to name a chunk of code and call it by name.