What’s one thing you learned? What’s still confusing?
Mini-Project: Number Guessing Game
Build a 1-100 guessing game with hot/cold feedback — your first program combining randomness, loops, and conditionals.
Mini-Project: FizzBuzz
Solve software's most famous interview problem — 1 to 100 with Fizz, Buzz, and FizzBuzz.
Functions: Reuse, Scope & First-Class Citizens
Define functions, master LEGB scope, *args/**kwargs, default arguments, lambdas, and closures.
Interactive Labs for This Track
Loop Visualizer
You're a factory robot repeating the same task on an assembly line — watch how loops automate repetitive work
List Slicing
You have a playlist of 50 songs — grab just tracks 10 through 20 with a single slice expression
Sorting Algorithms
You're organizing a library of 10,000 books — which sorting method is fastest?
Ask questions, share insights
if, for, and while underneath. Three keywords. That is genuinely the whole of it, and by the end of this lesson you will have written all three. Control flow is the spine of every program you'll ever write.if statement lets your program make decisions based on conditions:temperature = 35
if temperature > 30:
print("It is hot outside! Stay hydrated.")You just wrote your first decision! This is exactly how AI systems work — they check conditions and take different actions. You're already thinking like a programmer.
temperature > 30 is True, the indented code runs. If it is False, Python skips it entirely.score = 85
if score >= 90:
grade = "A"
print("Excellent work!")
elif score >= 80:
grade = "B"
print("Good job!")
elif score >= 70:
grade = "C"
print("Not bad, keep studying.")
elif score >= 60:
grade = "D"
print("You need to study more.")
else:
grade = "F"
print("Let's talk to your teacher.")
print(f"Your grade: {grade}")if is required and comes firstelif (short for "else if") is optional -- you can have as many as you wantelse is optional and comes last -- it catches everything that did not match aboveHit anIndentationErrororTabError? Mixing tabs and spaces, or forgetting to indent afterif:/for:, is the cause. See the error decoder for the fix.
What does this print? x = 5 if x > 10: print("big") elif x > 3: print("medium") elif x > 0: print("small")
x > 3 is True, so Python prints "medium" and jumps past the entire if/elif/else block without checking x > 0. This is why elif ordering matters: put your most specific conditions first. A common bug: writing elif x > 0: BEFORE elif x > 3: would print "small" for any positive number, never reaching "medium".age = 16
has_permit = True
has_glasses = False
# Combining with and/or
if age >= 16 and has_permit:
print("You can drive!")
# Nesting (if inside if)
if age >= 13:
print("You can create a social media account.")
if age >= 16:
print("You can also drive with a permit.")
if age >= 18:
print("You can vote!")
# Ternary (one-line if/else)
status = "adult" if age >= 18 else "minor"
print(f"Status: {status}")Optional shortcut: The ternary form below shows a compact way to write simple if/else. If it feels confusing, skip it for now — you can always come back to it later.
value_if_true if condition else value_if_false is great for simple decisions. Use the full if/else block for anything complex.for loop runs a block of code once for each item in a sequence:# Loop through a list
fruits = ["apple", "banana", "cherry"]
for fruit in fruits:
print(f"I like {fruit}")
# Output:
# I like apple
# I like banana
# I like cherryrange() generates a sequence of numbers. It is the most common way to run a loop a specific number of times:# range(5) generates: 0, 1, 2, 3, 4
for i in range(5):
print(f"Iteration {i}")
# range(start, stop) -- from start up to (but not including) stop
for i in range(1, 6):
print(i) # 1, 2, 3, 4, 5
# range(start, stop, step) -- with a step size
for i in range(0, 20, 5):
print(i) # 0, 5, 10, 15
# Count backwards
for i in range(10, 0, -1):
print(i) # 10, 9, 8, ... 1range(5) starts at 0 and goes up to (but does not include) 5. This "up to but not including" pattern appears everywhere in Python.How many times does for i in range(5) run?
i taking values 0, 1, 2, 3, 4. range(5) starts at 0 and stops before 5. This zero-indexing and exclusive-end pattern is consistent throughout Python (and most programming languages).for i in range(5): and generates the sequence [0, 1, 2, 3, 4]. It sets i = 0 and enters the loop body for the first time.for i in range(5): # i = 0
print(f"Step {i}: i squared is {i ** 2}")
# Output: Step 0: i squared is 0i = 1 and runs the loop body again. You do not need to manually increment i -- the for loop handles it.for i in range(5): # i = 1
print(f"Step {i}: i squared is {i ** 2}")
# Output: Step 1: i squared is 1i = 2 now. The expression i ** 2 evaluates to 4. Each iteration, the loop body executes with the new value of .Strings are sequences of characters, so you can loop through them:
word = "PYTHON"
for letter in word:
print(letter, end=" ")
# Output: P Y T H O NOften you need both the index and the value:
colors = ["red", "green", "blue"]
for index, color in enumerate(colors):
print(f"{index}: {color}")
# 0: red
# 1: green
# 2: bluefor actually does behind your backfor x in xs: line is sugar. Under the hood Python calls iter(xs) to get an iterator object, then calls next(it) in a loop until it sees StopIteration. The animation below opens up that mechanism — watch the iterator gear advance its pointer and StopIteration end the loop. Then try the four other presets (exhausted iterator, generators, infinite sources, zip) to see why this protocol is everywhere in Python.The animation above shows a canned 5-iteration loop. Now try it yourself: edit the code below, click Trace it, and watch Python execute line-by-line. Add a variable. Change the range. Introduce a bug. The trace updates with whatever you write.
Edit the code, then click Trace it. Python actually runs in your browser — every line, every variable, every print.
Click Trace it to capture the execution trace. The scrubber below will let you step through every variable change line-by-line.
while loop runs as long as a condition is True:count = 0
while count < 5:
print(f"Count is {count}")
count += 1 # DO NOT FORGET THIS or you get an infinite loop!
print("Done!")for loop: When you know how many times to repeat (iterate over a list, run 100 times)while loop: When you do not know how many times -- you repeat until something changes (keep asking for input until it is valid, train until loss is low enough)This code calls input(). Python here can't pause to ask you, so type the answers below before running — one value per line, in the order the program asks for them.
# Real-world while loop: input validation
password = ""
while password != "python123":
password = input("Enter the password: ")
if password != "python123":
print("Wrong! Try again.")
print("Access granted!")False, your loop runs forever:# DANGER: infinite loop!
# x = 1
# while x > 0:
# print(x)
# x += 1 # x keeps growing, always > 0
# To stop an infinite loop: press Ctrl+C in the terminalFalse. This usually means updating a variable inside the loop.break -- Exit the Loop Immediately# Find the first number divisible by 7
for i in range(1, 100):
if i % 7 == 0:
print(f"Found it: {i}")
break # stop the loop, do not check the rest
# Output: Found it: 7What is the final value of total? total = 0 for i in range(1, 5): total = total + i print(total)
i = 1, 2, 3, 4. After each iteration, total accumulates: 0 + 1 = 1, 1 + 2 = 3, 3 + 3 = 6, 6 + 4 = 10. This is the classic "accumulator" pattern — every running sum, every average, every count works this way. Note that range(1, 5) stops BEFORE 5, so we never add 5 (a common off-by-one error would give 15).continue -- Skip to the Next Iteration# Print only odd numbers
for i in range(10):
if i % 2 == 0:
continue # skip even numbers
print(i)
# Output: 1, 3, 5, 7, 9break in a While LoopThis code calls input(). Python here can't pause to ask you, so type the answers below before running — one value per line, in the order the program asks for them.
# A simple guessing game
import random
secret = random.randint(1, 20)
attempts = 0
while True: # loop forever (until we break)
guess = int(input("Guess a number 1-20: "))
attempts += 1
if guess < secret:
print("Too low!")
elif guess > secret:
print("Too high!")
else:
print(f"You got it in {attempts} attempts!")
break # exit the loopwhile True pattern with a break inside is very common. It means "loop forever until I explicitly say stop."What does `break` do inside a for loop?
You can put loops inside loops:
# Multiplication table
for i in range(1, 6):
for j in range(1, 6):
print(f"{i * j:4}", end="")
print() # new line after each row
# Output:
# 1 2 3 4 5
# 2 4 6 8 10
# 3 6 9 12 15
# 4 8 12 16 20
# 5 10 15 20 25Nested loops are how you process 2D data -- like pixels in an image (loop through rows, then columns) or a grid of weights in a neural network.
FizzBuzz is the most famous programming exercise. The rules are:
for i in range(1, 21):
if i % 3 == 0 and i % 5 == 0:
print("FizzBuzz")
elif i % 3 == 0:
print("Fizz")
elif i % 5 == 0:
print("Buzz")
else:
print(i)for, range(), if/elif/else, % (modulo), and and. If you understand every line, you have a solid grasp of control flow.Tests · Try each challenge. For challenge 5, the answer should be 154 (7 * 11 * 2).
This program is supposed to print the numbers 1 through 10 inclusive, then a total. But the last number is missing and the total is wrong. Fix the range.
1 2 3 4 5 6 7 8 9 10 Total: 55
Print every number from 1 to 30. Replace multiples of 3 with 'Fizz', multiples of 5 with 'Buzz', and multiples of both with 'FizzBuzz'.
1
2
Fizz
4
Buzz
Fizz
7
8
Fizz
Buzz
11
Fizz
13
14
FizzBuzz
... (continues to 30)# Print FizzBuzz from 1 to 30
for i in range(1, 31):
# TODO: if divisible by 3 AND 5 → "FizzBuzz"
# TODO: elif divisible by 3 → "Fizz"
# TODO: elif divisible by 5 → "Buzz"
# TODO: else → print the number
passControl flow is not just for toy problems. Here is how it shows up in real AI code:
# This is a simplified version of what happens when you train a model
epochs = 10
learning_rate = 0.01
loss = 100.0 # start with high loss
for epoch in range(epochs):
# In real code, this is where forward pass + backprop happens
loss = loss * 0.7 # simulate loss decreasing
if loss < 1.0:
print(f"Epoch {epoch}: Loss = {loss:.4f} -- Converged! Stopping early.")
break
if epoch % 2 == 0: # print every other epoch
print(f"Epoch {epoch}: Loss = {loss:.4f}")
print(f"Final loss: {loss:.4f}")# After a model outputs a probability, you make a decision
probability = 0.87
if probability > 0.9:
prediction = "definitely spam"
confidence = "high"
elif probability > 0.5:
prediction = "probably spam"
confidence = "medium"
else:
prediction = "not spam"
confidence = "high" if probability < 0.1 else "medium"
print(f"Prediction: {prediction} (confidence: {confidence})")# Filter out invalid data points
raw_data = [23, -1, 45, None, 67, 0, 89, -5, 100]
clean_data = []
for value in raw_data:
if value is None:
continue # skip missing values
if value < 0:
continue # skip negative values (invalid for this dataset)
clean_data.append(value)
print(f"Cleaned: {clean_data}") # [23, 45, 67, 0, 89, 100]
print(f"Kept {len(clean_data)} of {len(raw_data)} data points"):=) assigns AND returns a value in one expression — perfect for avoiding repetition in conditions:# Without walrus — compute twice
import re
data = "User age: 25"
if re.search(r'\d+', data):
match = re.search(r'\d+', data) # called twice!
print(f"Found: {match.group()}")
# With walrus — compute once
if match := re.search(r'\d+', data):
print(f"Found: {match.group()}") # match is available hereThe walrus operator is most useful in while loops and comprehensions:
# Classic pattern: read until empty
import sys
while line := sys.stdin.readline():
process(line)
# In comprehension — filter and transform without double call
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
# Without walrus — expensive function called twice
results = [expensive(x) for x in numbers if expensive(x) > 5]
# With walrus — call once, use twice
results = [y for x in numbers if (y := expensive(x)) > 5]match/case, which is much more powerful than a simple switch statement:# Basic value matching
command = "quit"
match command:
case "quit":
print("Exiting...")
case "help":
print("Available commands: quit, help, run")
case "run":
print("Running...")
case _: # _ is the wildcard (catches everything else)
print(f"Unknown command: {command}")# Match on type and destructure at the same time
def process_event(event):
match event:
case {"type": "click", "x": x, "y": y}:
print(f"Click at ({x}, {y})")
case {"type": "keypress", "key": key}:
print(f"Key pressed: {key}")
case {"type": "resize", "width": w, "height": h}:
print(f"Resized to {w}x{h}")
case _:
print("Unknown event")
process_event({"type": "click", "x": 100, "y": 200})
# Output: Click at (100, 200)
# Match sequences
def describe_list(lst):
match lst:
case []:
return "empty"
case [x]:
return f"single item: {x}"
case [x, y]:
return f"two items: {x} and {y}"
case [first, *rest]:
return f"starts with {first}, then {len(rest)} more"
# Match with guards (if conditions)
point = (3, -1)
match point:
case (x, y) if x == y:
print(f"Point on diagonal: ({x}, {y})")
case (x, y) if x > 0 and y > 0:
print(f"Point in quadrant I: ({x}, {y})")
case (x, y):
print(f"Point at ({x}, {y})")Match/case is more powerful than if/elif chains when you need to destructure data. It is especially useful for parsing JSON-like structures, handling API responses, and implementing command parsers.
*rest. Watch how each case is tested in order, how name captures (x, y, r, args) fly out as bindings, and how guards run only AFTER the structural match succeeds. The wildcard _ only fires when nothing else matched.if/elif/else lets your program make decisions -- check a condition, and run different code depending on whether it is True or False. Indentation defines the code blocksfor loops iterate over sequences -- use range(n) for a specific count, or loop directly over lists, strings, and other sequences. range(5) gives 0, 1, 2, 3, 4while loops repeat until a condition changes -- use when you do not know how many iterations you need. Always ensure the condition will eventually become Falsebreak exits a loop early, continue skips to the next iteration -- these give you fine-grained control over loop executionWhat is the output of: for i in range(3): print(i)
ifor i in range(5): # i = 2
print(f"Step {i}: i squared is {i ** 2}")
# Output: Step 2: i squared is 4i = 3 gives 9, i = 4 gives 16. After i = 4, there are no more values in the range, so the loop ends and Python moves to the next line after the loop.# Output: Step 3: i squared is 9
# Output: Step 4: i squared is 16
# Loop is done -- Python continues belowFive iterations, five outputs. The for loop is deterministic -- you know exactly how many times it will run before it starts. This is what makes it perfect for iterating over datasets, training batches, and epochs.
Step 0: i squared is 0
Step 1: i squared is 1
Step 2: i squared is 4
Step 3: i squared is 9
Step 4: i squared is 16