Loops and conditionals — the single most important thing in this whole course. Everything before this was setup; this is where scripts actually start doing real work.
A for-loop runs the same block of code once for every item in something — a list, a string, anything you can walk through one piece at a time (that's called an iterable). Each time through, the loop variable holds the next item.
fruits = ["apple", "banana", "cherry"]
for fruit in fruits:
print(fruit)
# apple
# banana
# cherry
colors = ["red", "blue"]. Loop over it, printing each one.
red then bluenums = [1, 2, 3]. Loop over it, printing each one.
1, 2, 3Loop over a 1-item list ["only"], printing it.
onlyLoop over an empty list [], printing each item. What happens?
letters = ["a", "b", "c"]. Loop over it, printing each one followed by "!".
a!, b!, c!Open problems/01_for_loop_basics.py and do all 5 problems locally.
Strings are iterable too — the same for walks through the characters, one at a time.
for ch in "cat":
print(ch)
# c
# a
# t
Loop over "hi", printing each character.
h then iLoop over "IT", printing each character.
I then TLoop over the single-character string "x".
xLoop over an empty string "". What happens?
Loop over "abc", printing type() of the loop variable each time.
<class 'str'> three times — each character is still a stringOpen problems/02_loop_string.py and do all 5 problems locally.
range() — Looping a Fixed Number of Timesrange(n) generates the numbers 0 up to (not including) n — useful when you want to loop a specific number of times, not over an existing list.
for i in range(3):
print(i)
# 0
# 1
# 2
Loop range(4), printing each number.
0, 1, 2, 3Loop range(1), printing it.
0Loop range(0). Predict, then run — what happens?
range(2, 5) — a second number means "start here instead of 0." Loop it and print each.
2, 3, 4Loop range(3), printing i + 1 each time.
1, 2, 3Open problems/03_range.py and do all 5 problems locally.
Start with an empty value before the loop, then update it a little each time through. total += n is shorthand for total = total + n — same thing, shorter, and what you'll see constantly in real code.
nums = [1, 2, 3]
total = 0
for n in nums:
total += n
print(total) # 6
nums = [1, 2, 3, 4]. Sum them with a loop and total += n. Print the total.
10Count how many items are in ["x", "y", "z"] by looping and incrementing a counter (count += 1) — don't use len().
3words = ["a", "b", "c"]. Build one combined string by looping and result += w. Print it.
abcSum an empty list [] the same way. Print the total.
0Count the characters in "hello" by looping and incrementing.
5Build a new list by looping [1, 2, 3] and .append()-ing each item into an empty list. Print the result.
[1, 2, 3]Open problems/04_accumulator.py and do all 6 problems locally.
This is the payoff Module 5 was building toward — you can finally process every record, not just one at a time by index.
from fixture import USERS
for user in USERS:
print(user["id"])
# 00u001
# 00u002
# ... all 12
Loop USERS, printing each id.
00u001 through 00u012Loop USERS, printing each status.
Loop USERS, printing each profile["email"].
Count how many total records there are by looping and incrementing — confirm it matches len(USERS).
12Loop USERS, building a list of every id using .append() instead of printing directly. Print the final list.
Open problems/05_real_users_loop.py — 5 problems, #5 is a checkpoint combining sub-skills 1–4.
if Statementif runs a block of code only when a condition is True. If it's False, the block is skipped entirely — nothing happens.
status = "ACTIVE"
if status == "ACTIVE":
print("active user")
status = "ACTIVE". If it equals "ACTIVE", print "running".
runningstatus = "SUSPENDED". Same check as above. What prints?
count = 5. If count > 0, print "has items".
has itemsgroups = []. If bool(groups), print "has groups" — reuses Module 1's truthiness.
email = "test@example.com". If "@" in email, print "looks valid".
looks validOpen problems/06_if_statement.py and do all 5 problems locally.
if / elseelse runs when the if's condition was False — one of the two blocks always runs, never both, never neither.
status = "SUSPENDED"
if status == "ACTIVE":
print("active")
else:
print("not active")
status = "ACTIVE". Print "active" if so, else "not active".
activestatus = "DEPROVISIONED". Same check.
not activegroups = []. Print "has groups" if truthy, else "no groups".
no groupscount = 0. Print "empty" if count == 0, else print the count.
emptylogin = None. Print "never logged in" if login is None, else print login.
never logged inOpen problems/07_if_else.py and do all 5 problems locally.
elif Chainselif ("else if") checks another condition only if the ones before it were False. Python checks top to bottom and stops at the first match — you can chain as many as you need, ending in an optional else for "none of the above."
status = "SUSPENDED"
if status == "ACTIVE":
print("running")
elif status == "SUSPENDED":
print("paused")
else:
print("gone")
status = "ACTIVE". Run the three-way check above. What prints?
runningstatus = "DEPROVISIONED". Same check.
gonescore = 85. Print "A" if >= 90, "B" if >= 80, else "C".
Bscore = 95. Same check.
A (stops at the first match)dept = "Sales". Print "Engineering team" if Engineering, "Security team" if Security, else "Other team".
Other teamLoop ["ACTIVE", "SUSPENDED", "DEPROVISIONED"], printing "running"/"paused"/"gone" for each using the three-way check.
running, paused, goneOpen problems/08_elif.py and do all 6 problems locally.
Put an if inside a for to act on only some of the items — this combination is most of what real scripts actually do.
statuses = ["ACTIVE", "SUSPENDED", "ACTIVE"]
for s in statuses:
if s == "ACTIVE":
print(s)
nums = [1, -2, 3, -4]. Loop and print only the ones > 0.
1, 3names = ["Amy", "Bo", "Charlotte"]. Print only the ones with len() > 3.
Charlotteemails = ["a@x.com", "b", "c@x.com"]. Print only the ones containing "@".
a@x.com, c@x.comgroups = [[], ["IT"], []]. Print only the non-empty ones.
['IT']Loop USERS, printing the id only for records where status == "SUSPENDED".
Loop USERS, printing the email only for records where status == "DEPROVISIONED".
Open problems/09_filter_loop.py and do all 6 problems locally.
Combine the accumulator pattern with a filter — only increment when the condition matches.
from fixture import USERS
count = 0
for user in USERS:
if user["status"] == "ACTIVE":
count += 1
print(count)
Count how many USERS records have status == "ACTIVE".
8Count how many have status == "SUSPENDED".
2Count how many have status == "DEPROVISIONED".
2Confirm your three counts add up to len(USERS) by printing all four numbers.
8\n2\n2\n12Count how many USERS records have profile["department"] == "Engineering" — combines filtering (9) and accumulation (4) on nested data.
3Open problems/10_count_with_condition.py — 5 problems, #5 is a checkpoint combining sub-skills 6–9.
Looping a dict directly gives you its keys, one at a time — .keys() does the exact same thing, spelled out explicitly.
d = {"a": 1, "b": 2}
for k in d:
print(k)
# a
# b
d = {"x": 1, "y": 2}. Loop it directly, printing each key.
x then ySame dict. Loop d.keys() instead — same result.
x then yscores = {"math": 90, "art": 85}. Loop it, printing each key.
math then artLoop an empty dict {}. What happens?
d = {"x": 1, "y": 2}. Loop the keys, and for each one print d[key] too — two lines per key.
x\n1\ny\n2Open problems/11_dict_keys.py and do all 5 problems locally.
.values() loops the values instead of the keys.
d = {"a": 1, "b": 2}
for v in d.values():
print(v)
# 1
# 2
d = {"x": 1, "y": 2}. Loop d.values(), printing each.
1 then 2scores = {"math": 90, "art": 85}. Loop the values, sum them with an accumulator.
175Loop an empty dict's .values(). What happens?
d = {"a": 5, "b": -3, "c": 2}. Loop the values, printing only the ones > 0 (filtering, from sub-skill 9).
5 then 2Loop d.values() for {"active": True, "verified": False}, counting how many are True.
1Open problems/12_dict_values.py and do all 5 problems locally.
.items() gives you both the key and value together, each time through. This needs two loop variables instead of one — Python unpacks the pair automatically.
d = {"a": 1, "b": 2}
for k, v in d.items():
print(k, v)
# a 1
# b 2
d = {"x": 1, "y": 2}. Loop d.items() with two variables, print both each time.
x 1 then y 2scores = {"math": 90, "art": 85}. Loop items, printing an f-string like "math: 90".
math: 90 then art: 85d = {"a": 5, "b": -3}. Loop items, printing only the pairs where the value > 0.
a 5Loop an empty dict's .items(). What happens?
d = {"a": 1}. What happens with for k in d.items(): print(k) — only one loop variable this time?
('a', 1)With one loop variable, each item is the whole key-value pair together, shown in parentheses. Two variables is what unpacks it into separate pieces.
Open problems/13_dict_items.py and do all 6 problems locally.
A loop inside a loop — walk every user, and for each one, walk their groups too. Remember: each group is a dict ({"id": ..., "name": ...}), so grab ["name"] from it.
for user in USERS:
print(user["profile"]["firstName"])
for g in user["groups"]:
print(" " + g["name"])
Loop USERS, and for each one, loop their groups, printing each group's name.
Same nested loop, but print the user's firstName before each user's group names.
For just USERS[0] (Julio), loop his groups and print each name.
Engineering then VPN-UsersCount the total number of group memberships across all users (accumulator + nested loop).
15Dana (USERS[3]) has an empty groups list. If you print her firstName, then loop and print her group names, what shows up for the inner loop?
This is a real gotcha, not a crash: an empty list means the nested loop silently does nothing. Three users in your fixture (Dana, Tri, Pat) have this — easy to not notice if you're not looking for it.
Loop USERS, printing firstName for every user, then their group count (len()) right after — confirm the zero-group ones print 0, not an error.
Open problems/14_nested_loops.py and do all 5 problems locally.
Combine everything so far: filter which users you look at, then loop into their nested data.
for user in USERS:
if user["status"] == "ACTIVE":
for g in user["groups"]:
print(g["name"])
Print every group name, but only for ACTIVE users.
Print every group name, but only for SUSPENDED users.
Count total group memberships across only ACTIVE users (accumulator + filter + nested loop).
13Print the firstName of every ACTIVE user who has at least one group (skip the ones with zero, using an if on the group list's truthiness).
Loop USERS; for each ACTIVE user, print their firstName, then loop their groups printing each name indented — a real filtered nested report. Combines sub-skills 9–14.
Open problems/15_nested_plus_conditional.py — 5 problems, #5 is a checkpoint combining sub-skills 9–14.
You've seen a single missing key crash before. Now watch what it looks like across a full loop — this is the single most realistic bug shape in IT scripting.
Loop USERS, printing user["profile"]["department"] for every record. Will it finish all 12? If not, how far does it get?
for user in USERS:
print(user["profile"]["department"])KeyError: 'department'Records 1–7 work fine, then record 8 (Layla, USERS[7]) has no department key and the whole script dies. This is exactly why "it worked when I tested it" isn't good enough — you tested on the records that happened to be complete.
Confirm it yourself: run the loop above, count how many lines printed before the crash.
7Print USERS[7]["profile"] directly — see for yourself that department is missing.
department key in the dictLoop just USERS[0:3] (a slice) the same way — no crash, since none of the first 3 are missing the key.
Loop USERS printing lastLogin for every record — this one won't crash. Why not, even though Dana's is None?
None is fine — it only crashes when you try to use it, like gluing text onto itOpen problems/16_missing_key_crash.py and do all 4 problems locally.
ifFix the crash from last section: check whether the key exists before using it, so the loop can keep going instead of dying.
for user in USERS:
profile = user["profile"]
if "department" in profile:
print(profile["department"])
else:
print("Unknown")
Run the guarded loop above. Confirm it finishes all 12 records without crashing.
UnknownSame idea, but use .get("department", "Unknown") instead of the if/in check — shorter, same result.
Loop USERS, printing lastLogin using .get("lastLogin", "Never") — Dana's None stays None though, since the key IS present, just empty. Notice the difference from missing entirely.
None, not NeverCount how many users are missing a department key, using the in check inside a loop with an accumulator.
1Loop USERS, printing f"{firstName}: {department}" using the guard pattern, for every record, no crash.
UnknownOpen problems/17_guard_pattern.py and do all 5 problems locally.
Loop one dataset, and for each record, look something up in a completely different one — this is how you'd combine two separate API responses in real life.
for user in USERS:
email = user["profile"]["email"]
count = LOGIN_COUNTS.get(email, 0)
print(f"{email}: {count}")
Run the join loop above for all 12 users.
0Kelly O'Connor's real email is kokonnor@example.com. What will her line in the loop above show?
kokonnor@example.com: 0 — wrong, not crashedThis is the real typo bug from your fixture: LOGIN_COUNTS has "koonnor@example.com" misspelled, so the lookup silently misses and falls back to the default. No error, no warning — just quietly wrong data. This is worse than a crash, because nothing tells you to go look.
Count how many users have a login count of exactly 0 from the join (genuinely missing, typo'd, or actually recorded as 0).
5Loop USERS, printing only the ones where the join found a count > 0.
Print the total of all login counts found across every user, using an accumulator.
589Open problems/18_join_in_loop.py and do all 4 problems locally.
A one-line loop that builds a new list — the same "filter inside a loop" pattern from sub-skill 9, just compressed onto one line.
# The long way:
active_emails = []
for u in USERS:
if u["status"] == "ACTIVE":
active_emails.append(u["profile"]["email"])
# The comprehension way -- same result:
active_emails = [u["profile"]["email"] for u in USERS if u["status"] == "ACTIVE"]
nums = [1, 2, 3, 4]. Build a list comprehension that keeps only the ones > 2.
[3, 4]Build a comprehension of every user's id from USERS (no filter this time).
Build a comprehension of firstName for every SUSPENDED user.
['Bina', 'Rosa']Build the active-emails comprehension from the example, then print its len().
8Build a comprehension of every group name a user has, for just USERS[9] (Marcus) — comprehension over a nested list.
['Engineering', 'Admins', 'VPN-Users']Open problems/19_list_comprehension.py and do all 5 problems locally.
Everything in this module, combined into one real script over all 12 records.
Loop all USERS, and using the guard pattern, print f"{firstName} ({dept})" for each — no crash on Layla.
For each user, nested-loop their groups and count the total, printing f"{firstName}: {group_count} groups".
0 groupsUsing a list comprehension, build the list of every ACTIVE user's email, print it and its length.
Loop and join with LOGIN_COUNTS, counting how many users have 0 logins found (missing, typo'd, or actually recorded as 0).
5Using .items(), loop LOGIN_COUNTS directly and print every email/count pair as f"{email}: {count}".
One script, using every sub-skill 1–19 at least once: loop all USERS (filter to ACTIVE only), guard the department lookup, nested-loop groups and count them, join with LOGIN_COUNTS, and finish with a comprehension summarizing all active emails. Print a report line per active user, then the final email list.
Open problems/20_full_fixture_report.py — 6 problems, #6 is the final checkpoint for all of Module 6.
Run python check.py in problems/ to grade everything.