Control Flow & Functions
So far your programs run straight down, every line once. Real programs make choices ("if logged in, show the dashboard") and repeat work ("for each order, send an email"), and once you've written useful logic, you want to name it and reuse it instead of copy-pasting. Deciding, repeating, and packaging - control flow and functions - are where code starts to feel powerful.
Remember the indentation rule from Phase 2: : opens a block and the
indented lines beneath belong to it. Every structure here uses it.
if / elif / else - making a decision
What it actually is. An if runs a block only when a condition is true. Add elif ("else if") for
more conditions, and else for "none of the above." Python checks top to bottom, runs the first
matching block, then skips the rest.
= 72
What just happened: Python checked score >= 90 (false), then >= 80 (false), then >= 70 (true),
ran that block, and stopped without reaching else:
C
As a picture - Python falls through the checks until one matches:
📝 Terminology. A condition is any expression evaluating to True or False - usually a
comparison like >=, ==, != (not equal), <, >. The block under if runs when it's True.
Truthiness - what counts as "true"
What it actually is. Python lets you use any value as a condition, not only True/False - it
asks "is this truthy or falsy?" The falsy values are "empty or nothing"; almost everything else is
truthy.
What just happened: bool() shows how Python judges each value as a condition: zero, the empty
string, the empty list, and None are all falsy; a nonzero number, non-empty string, and non-empty
list are truthy:
False False False False
True True True
This enables natural checks - instead of if len(items) > 0:, write:
=
What just happened: The empty list is falsy, so the else ran:
The list is empty
💡 Key point. "Empty or zero or nothing" is falsy; everything else truthy. if my_list: reads as
"if the list has anything in it" - clean and Pythonic.
for - do something for each item
What it actually is. A for loop walks a collection, running its block once per item, each handed
to a name you choose.
What just happened: The loop took each item in turn, pointed fruit at it, and ran the block - three
items, three runs:
apple
banana
cherry
To repeat a fixed number of times, loop over range(n), which produces 0 up to (but not including)
n:
What just happened: range(3) yielded 0, 1, 2 - stopping before 3, the same "stop is
exclusive" rule as slicing:
0
1
2
while - repeat until a condition turns false
What it actually is. A while loop repeats its block as long as a condition stays true. Reach for
it when you don't know in advance how many times you'll loop - you loop until something changes.
= 3
= - 1
What just happened: The loop ran while n > 0, printing n then shrinking it by 1 each pass, until it
hit 0 and the condition went false:
3
2
1
⚠️ The infinite loop. A while only stops when its condition becomes false, so something inside the
loop must move it toward false. Drop the n = n - 1 line above and n stays 3 forever, printing without
end. If a program ever "hangs," suspect an infinite loop; press Ctrl-C to stop it.
Functions - name a piece of logic and reuse it
What it actually is. A function is a named, reusable block of instructions: define it once with
def, then call it whenever needed, with different inputs each time - how you avoid copy-pasting logic.
return f
What just happened: def greet(name): defined a function taking one parameter, name; return
hands a value back. Each call supplied a different name, giving two different results:
Hello, Ada!
Hello, Linus!
📝 Terminology. A parameter is the name in the definition (name); an argument is the actual
value passed when calling ("Ada"). return sends a value back out of the function.
Defaults let a parameter be optional by giving it a fallback value:
return f
What just happened: Without a greeting argument, it falls back to "Hello"; supply one and yours
wins:
Hello, Ada!
Hi, Ada!
return vs printing - a crucial difference. A function that prints shows text but hands back
nothing usable; one that returns gives a value you can store and work with. No return means it hands
back None:
=
What just happened: show(5) printed 5, but with no return, the call evaluated to None - stored
in result and printed on the second line:
5
None
To use a function's output later, it must return it, not just print it - printing is for humans,
returning is for feeding the rest of your program.
The classic trap: mutable default arguments
This one bites experienced developers too, so it's worth meeting head-on.
What goes wrong. Give a parameter a mutable default (like a list), and it's created once, when the function is defined - then shared across every call, never fresh, which is almost never what you want.
return
What just happened: You'd expect each call to start with an empty basket. Instead both share the same default list, so the second call sees the first call's leftovers:
['apple']
['apple', 'banana']
That ['apple', 'banana'] on the second line is the bug - "apple" shouldn't be there. The shared
default silently accumulates across calls.
The fix - a pattern to adopt every time: default to None, then create a fresh value inside the
function.
=
return
What just happened: Now each call with no basket gets a brand-new list, so they don't bleed together:
['apple']
['banana']
⚠️ Never use a mutable default ([], {}, set()) directly. Default to None and build the real
value inside the function. Memorize this - it's a genuine sharp edge, coming straight from the aliasing
idea in Phase 3: the default list is one shared object.
Recap
if/elif/elseruns the first matching block. Conditions are expressions evaluating toTrue/False.- Truthiness: empty/zero/
Noneare falsy, everything else truthy - soif my_list:means "if it has items." forloops once per item (userange(n)for a count);whileloops until its condition goes false - make sure something moves it there.defdefines a function; parameters name its inputs,returnhands a value back. Defaults make parameters optional.returngives a usable value; a function with noreturnyieldsNone.- Never use a mutable default argument. Default to
Noneand build the list/dict inside the function.
Next: importing code, the standard library, writing your own modules, and laying out a real project.
← Phase 3: Collections · Guide overview · Phase 5: Modules & Project Layout →
Before the quiz: without looking back, say (or jot down) the core idea of this phase in your own words.
Check your understanding 3 questions
1. In Python, which values are falsy?
2. What is the difference between `return` and printing in a function?
3. Why should you never use a mutable default argument like `def f(x, items=[]):`?