1. How code runs — compiled vs interpreted
Session 1's hardware/software interface. A compiler translates the whole source to machine code once, then the CPU runs it; an interpreter walks the source statement by statement every run. Step the pipeline and watch where time is spent.
2. Flow control & loops
A for loop over range(n) with an if guard. Choose what happens when the guard fires — continue skips the rest of the body, break exits the loop. Step to trace the control flow and the running total.
3. Recursion & the call stack
Session 6 — a function calling itself. Each call pushes a frame holding its own arguments; frames pop as they return, multiplying results back up. Step through $\texttt{fact}(n)$ or $\texttt{fib}(n)$ and watch the stack grow and unwind.
4. Recursion tree & memoization
Naive $\texttt{fib}(n)$ recomputes the same subproblems exponentially often. Memoizing — caching each result the first time — collapses the tree to $O(n)$ calls. Toggle the cache and compare the call counts.
Green nodes were computed; faint nodes were served from the cache.
5. Scope, lifetime & closures
Session 7 — local vs global names and side effects. Python resolves a name by the LEGB rule: Local, Enclosing, Global, Built-in. Pick a scenario and trace which binding each reference resolves to, including a closure that captures an enclosing variable.
6. Higher-order functions — map · filter · reduce
Session 7's functional tools. A lambda is applied across a list: map transforms every element, filter keeps those where the predicate is true, reduce folds them to one value. Step to see each element flow through the pipeline.
7. Eager vs lazy evaluation (generators)
A list builds all its values up front; a generator with yield produces them one at a time, only when pulled. With a take(k) downstream, the lazy pipeline computes just what it needs. Step the consumer and compare work done.
8. Mutability & function side effects
Session 12 — "modify in-place" vs "return a new object". Python passes object references: mutating a list inside a function is visible to the caller, but rebinding the name, or working on a copy, is not. Run each variant and watch the caller's list.
9. Dictionaries as hash maps
Session 10 — key/value pairs. A dict hashes each key, then hash % buckets chooses a slot. Two keys landing in the same slot collide and chain together — which is why average lookup is $O(1)$ but degrades when buckets fill. Add keys and watch them distribute.
10. NumPy — vectorization & broadcasting
Module 4 — array programming. A vectorized op applies element-wise across a whole array at once; broadcasting stretches a smaller shape to match without copying. Pick operands and see which cells combine and whether the shapes are broadcast-compatible.