Exercises — Python syntax, data types, control flow
Before we start, one picture ties everything together — how Python decides which block of code runs.

Read the figure like a road: you enter at the top, each diamond asks a yes/no question, and you follow the first branch whose answer is "yes". That single idea (checked top to bottom, stop at first match) explains almost every control-flow trap on this page.
Level 1 — Recognition
You are shown code; you predict the exact output. No cleverness, just knowing the rules.
Exercise 1.1 — int vs float division
a = 7 / 2
b = 7 // 2
c = 7 % 2
print(a, b, c)What is printed, and what type is each of a, b, c?
Recall Solution
WHAT each operator does
/is true division — it always returns afloat, even when the numbers divide evenly. So7 / 2 = 3.5.//is floor division — it divides then rounds down to the nearest whole number, and stays anintwhen both inputs areint. So7 // 2 = 3.%is modulo — the remainder left after floor division. , so the remainder is1.
Output: 3.5 3 1
Types: a is float, b is int, c is int.
WHY it matters: in a training loop, number-of-steps must be a whole number, so you reach for //. Using / gives you 3.5 — meaningless as a step count.
Exercise 1.2 — Truthiness
values = [0, "", [], "0", [0], None]
for v in values:
print(bool(v))Predict all six lines.
Recall Solution
Python calls something falsy if it counts as False in a condition. The falsy family is: the number 0, empty containers ("", [], {}, set()), and None. Everything else is truthy.
Go item by item:
0→False(the number zero)""→False(empty string)[]→False(empty list)"0"→True— this is a string of length 1, not the number zero. It contains a character, so it is non-empty.[0]→True— a list with one element. The element happens to be0, but the list is non-empty.None→False
Output: False, False, False, True, True, False.
Level 2 — Application
Now you compute a real answer that an ML script would need.
Exercise 2.1 — Steps per epoch
A dataset has dataset_size = 1000 samples and batch_size = 32. Using floor division, how many full batches fit in one epoch, and how many samples are left over in a final partial batch?
Recall Solution
- Full batches:
1000 // 32. Since and , the answer is31. - Leftover:
1000 % 32 = 1000 - 992 = 8.
Answer: 31 full batches, plus 8 leftover samples (one partial batch).
WHY floor division: you cannot run 0.25 of a weight update, so the count must be an integer — exactly what // guarantees.
Exercise 2.2 — Batch start indices
Predict the exact list produced by
list(range(0, 1000, 32))and state how many elements it has.
Recall Solution
range(start, stop, step) produces start, start+step, start+2·step, … and stops before reaching stop. Here: 0, 32, 64, … up to the largest multiple of 32 that is still < 1000.
That largest value is 992 (since 1024 ≥ 1000 is excluded). The values are for — that is 32 elements, the numbers 0, 32, 64, …, 992.
Answer: first few are [0, 32, 64, 96, ...], last is 992, total length 32.
Note this is one more than the 31 full batches — the 32nd start index (992) begins the partial batch of the leftover 8 samples.
Exercise 2.3 — Step-decay learning rate
Using the parent note's schedule, compute lr for epoch = 50, initial_lr = 0.1:
if epoch < 30: lr = initial_lr
elif epoch < 60: lr = initial_lr * 0.1
elif epoch < 90: lr = initial_lr * 0.01
else: lr = initial_lr * 0.001Recall Solution
Conditions are checked top to bottom, stop at first true (follow the road in the figure above).
50 < 30? No.50 < 60? Yes → run this block, then skip the rest.
lr = 0.1 * 0.1 = 0.01.
Answer: lr = 0.01.
Level 3 — Analysis
Here you must reason about why Python behaves as it does, especially around references.
Exercise 3.1 — Aliasing vs copy
w = [0.5, 0.3, 0.8]
backup = w
backup[0] = 0.9
print(w[0])What prints, and why?
Recall Solution
backup = w does not copy the list. It makes backup a second name pointing at the same list object (this is reference semantics — see the figure below).

Both names point to one box in memory. Editing through backup edits the one and only list, so w sees it too.
Output: 0.9.
To get an independent copy you need backup = w.copy() (shallow) or copy.deepcopy(w) (for nested structures).
Exercise 3.2 — Shallow copy limitation
grid = [[1, 2], [3, 4]]
shallow = grid.copy()
shallow[0][0] = 99
print(grid[0][0])Does grid change? Explain.
Recall Solution
.copy() makes a shallow copy: a new outer list, but its elements are the same inner list objects. So shallow[0] and grid[0] are still the same inner list. Editing shallow[0][0] edits that shared inner list.
Output: 99 — yes, grid changes.
Only copy.deepcopy(grid) would rebuild the inner lists too, giving full independence.
Exercise 3.3 — LEGB name resolution
lr = 0.01
def train():
lr = 0.001
def update():
print(lr)
update()
train()What prints, and which scope wins?
Recall Solution
Python resolves a name by the LEGB order: Local → Enclosing → Global → Built-in, using the first match.
Inside update, lr is not local (never assigned there). Python steps out to the Enclosing function train, where lr = 0.001 exists. Match found — it stops there and never reaches the global 0.01.
Output: 0.001.
Level 4 — Synthesis
Combine loops, comprehensions, and conditions into one result.
Exercise 4.1 — Filtered comprehension
Evaluate exactly:
[x ** 2 for x in range(10) if x % 2 == 0]Recall Solution
Read it as a loop: "for each x in 0..9, keep it only if x % 2 == 0 (even), then output x**2."
Even values of x: 0, 2, 4, 6, 8. Their squares: 0, 4, 16, 36, 64.
Answer: [0, 4, 16, 36, 64].
Exercise 4.2 — Nested loop count
count = 0
for i in range(3):
for j in range(i + 1):
count += 1
print(count)What is count?
Recall Solution
The inner loop runs i + 1 times for each i:
i = 0→ inner runs1timei = 1→ inner runs2timesi = 2→ inner runs3times
Total: .
Answer: 6.
Exercise 4.3 — while-loop convergence
loss = 1.0
steps = 0
while loss > 0.1:
loss = loss / 2
steps += 1
print(steps, loss)How many steps, and the final loss?
Recall Solution
Each pass halves loss. Track it (the condition is re-checked before each pass):
- start
1.0> 0.1 → halve →0.5, steps 1 0.5> 0.1 →0.25, steps 20.25> 0.1 →0.125, steps 30.125> 0.1 →0.0625, steps 40.0625> 0.1? No → loop stops.
Answer: steps = 4, loss = 0.0625.
Level 5 — Mastery
One problem that only works if every earlier idea is solid.
Exercise 5.1 — Mini training-schedule audit
dataset_size = 1000
batch_size = 32
initial_lr = 0.1
steps_per_epoch = dataset_size // batch_size
starts = list(range(0, dataset_size, batch_size))
def lr_at(epoch):
if epoch < 30: return initial_lr
elif epoch < 60: return initial_lr * 0.1
elif epoch < 90: return initial_lr * 0.01
else: return initial_lr * 0.001
total_updates = 0
for epoch in range(3): # epochs 0, 1, 2
total_updates += len(starts)
print(steps_per_epoch,
len(starts),
lr_at(45),
total_updates)Predict all four printed values.
Recall Solution
Solve each piece with the tools above:
steps_per_epoch = 1000 // 32 = 31(Ex 2.1).len(starts)—range(0,1000,32)has 32 elements (Ex 2.2). Note this differs fromsteps_per_epochby one, because the 32nd start begins the partial batch of 8 leftover samples.lr_at(45):45 < 30? no.45 < 60? yes →0.1 * 0.1 = 0.01(Ex 2.3 logic).total_updates: the loop runs forepochin0,1,2→ 3 epochs, each addinglen(starts) = 32. Total .
Output: 31 32 0.01 96.
The lesson: steps_per_epoch (31) and the number of batch starts (32) are not the same number — one uses // (full batches), the other uses range (which includes the partial batch's start). Confusing them under- or over-counts your updates.
Recall Quick self-check (cloze)
/ always returns type ::: float
7 // 2 equals ::: 3
7 % 2 equals ::: 1
bool([0]) is ::: True
The name-resolution order is ::: LEGB (Local, Enclosing, Global, Built-in)
range(0, 1000, 32) has how many elements ::: 32
b = a for a list makes b a ::: reference (alias) to the same object, not a copy
Where to go next: these ideas power NumPy arrays and vectorization (replacing slow loops with array ops), Python functions and lambda (scope and LEGB in depth), and Python iterators and generators (what for really does under the hood). Reference semantics reappear everywhere in Pandas DataFrames and Object-oriented programming in Python.