Yesterday's generate_skus walked every aisle number with range(). Let me ask you something concrete: you're scanning products in aisle 7 and you find the one you're looking for on the third shelf. What do you do?
I stop scanning. I got what I came for — no reason to check the other forty shelves.
Right. But what does Python do if you don't tell it to stop?
It keeps going. Checks every remaining item even though you already found what you needed.
That's the gap break fills. Today covers three things that change how loops execute: break stops the loop entirely, continue skips the current item and moves to the next, and the else clause on a loop only runs when the loop finishes without hitting a break. All three fit the same scanner analogy.
The else clause on a loop — not on an if? I didn't know loops had an else.
They do, and it surprises almost everyone the first time. Picture the warehouse scanner. break is "stop scanning, I found what I need." continue is "skip this item, it's damaged, move to the next one." And the loop else is the sign that prints when you finish the whole aisle and never triggered a stop: "Not found in this aisle." It only runs if break never fired.
So if the loop runs all the way through naturally, else executes. If break cuts it short, else is skipped entirely?
Exactly. And this lets you express "I searched everything and found nothing" without a flag variable. Here's the function you're building today:
def find_low_stock(products: list[dict], threshold: int = 10) -> dict | None:
for product in products:
if product["stock"] < threshold:
break
else:
return None
return productreturn product is outside the loop? And product holds the item that triggered the break? Loop variables survive after the loop exits?
They do. Python doesn't clean up loop variables when the loop ends — they hold whatever value they had on the last iteration. When break fires mid-loop, product is the item that triggered it. So returning it directly works.
And if the loop runs to completion without breaking, the else returns None. So the function either returns the first under-threshold product, or None if everything is fine.
No flag variable, no index tracking, no found = False sentinel. The for/else pattern encodes the "did I find it?" question directly in the loop structure.
I've written the found = False version. Multiple times. For the deduplication logic in Track 1.
Nearly everyone does. The flag works — it's just noisier. Here's the side-by-side:
# Flag pattern — works, carries extra weight
def find_low_stock_flag(products: list[dict], threshold: int = 10) -> dict | None:
found = None
for product in products:
if product["stock"] < threshold:
found = product
break
return found
# for/else — same logic, no sentinel variable
def find_low_stock(products: list[dict], threshold: int = 10) -> dict | None:
for product in products:
if product["stock"] < threshold:
break
else:
return None
return productThe second one makes the "not found" case explicit instead of hiding it in a None default. I can read the intent without tracing the flag.
Now continue — the scanner skipping a damaged item. When Python hits continue, it immediately jumps to the next iteration, skipping everything below it in the current pass:
def count_sellable_stock(products: list[dict]) -> int:
total = 0
for product in products:
if product.get("damaged", False):
continue # skip damaged items
if product["stock"] == 0:
continue # out of stock — skip
total += product["stock"]
return totalcontinue doesn't end the loop, it ends this pass. The loop's mission is still running — it just moves to the next item.
Right. And break and continue can appear in the same loop — different triggers, different outcomes. A scanner might skip damaged items with continue but stop entirely when it hits a recalled SKU with break:
QUARANTINE_SKUS = {"SKU-9001", "SKU-9002"}
def scan_for_report(products: list[dict]) -> list[dict]:
report = []
for product in products:
if product["sku"] in QUARANTINE_SKUS:
break # stop everything immediately
if product.get("damaged", False):
continue # skip and keep scanning
report.append(product)
return reportThe quarantine check stops the whole scan — nothing after that point enters the report. A damaged item gets excluded but scanning continues. Two keywords, two genuinely different business rules.
Business rules expressed as control flow — that's what makes code readable to someone who knows the domain, not just someone who knows Python. One edge case worth knowing: break and continue only affect the innermost loop. Nested loops require a different approach.
So if I'm iterating aisles, and inside that iterating shelves, a break inside the shelf loop exits the shelf scan but the aisle loop keeps going?
Exactly. The outer loop doesn't know the inner loop broke. If you need to exit both at once, extract the inner work into a function and use return:
def find_in_warehouse(aisles: list[list[dict]], sku: str) -> dict | None:
for aisle in aisles:
for product in aisle:
if product["sku"] == sku:
return product # exits both loops immediately
return Nonereturn is the real break-out-of-everything. Extracting to a function also gives the logic a name, which makes it readable regardless.
You just described one of the most common refactoring patterns in real Python code. Let's verify one edge case for find_low_stock before we close:
Empty list — the loop body never runs, break never fires, else executes, returns None. That's the correct behavior and it costs nothing to handle. No special-casing required.
Diane is not going to get a crash on an empty inventory batch. That's your whole job.
"Maya Patel: Warehouse Incident Prevention." It doesn't fit on a business card but it's accurate.
Tomorrow: match — structural pattern matching. If today was bending loops to your will, tomorrow is routing data structures to the right handler. That giant if/elif chain you wrote for sales report categories? Tomorrow you finally see what Python was always trying to give you.
I remember that chain. Eight elif blocks. I hated every one of them.
Three loop control statements modify how a for or while loop executes:
break — exits the loop immediately, skipping the else clausecontinue — skips the rest of the current iteration, proceeds to the nextelse on a loop — runs when the loop exhausts its iterable naturally (never triggered if break fires)def find_low_stock(products: list[dict], threshold: int = 10) -> dict | None:
'Return the first product with stock below threshold, or None.'
for product in products:
if product["stock"] < threshold:
break # found one — exit loop, skip else
else:
return None # loop finished without break — nothing found
return product # product holds the item that triggered breakLoop variable survival: Python does not clean up loop variables when the loop exits. After break, the loop variable holds the value from the iteration that triggered the break. This is what makes the pattern work without a temporary variable.
for/else vs flag pattern:
# Flag pattern — works but adds noise
found = None
for p in products:
if p["stock"] < threshold:
found = p
break
return found
# for/else — same logic, no sentinel
for p in products:
if p["stock"] < threshold:
break
else:
return None
return pEdge cases:
else executes immediatelycontinue and break in the same loop: independent statements, different triggersbreak only exits the innermost loop — use return from a helper function to exit nested loopsPitfall 1: Forgetting that else is "no break", not "loop condition false". The loop else runs whenever the loop ends without a break — including when the iterable is empty. For a while loop it runs when the condition becomes False naturally.
Pitfall 2: Expecting break to exit all nested loops. A break inside a nested for only exits the innermost loop. Extract nested search logic into a function and use return to exit cleanly from any depth.
Pitfall 3: Misplacing code after break. Any code between a break and the end of the loop body is unreachable but does not raise an error. Linters flag this; the loop just stops before reaching it.
while loops also support break, continue, and else. The canonical use is a polling loop that breaks when a condition is satisfied and uses else to handle the timeout case:
attempts = 0
while attempts < 5:
result = check_api()
if result.ok:
break
attempts += 1
else:
raise TimeoutError("API did not respond after 5 attempts")
process(result)Python has no labeled break (break 2) — always refactor nested search logic into a named function.
Sign up to write and run code in this lesson.
Yesterday's generate_skus walked every aisle number with range(). Let me ask you something concrete: you're scanning products in aisle 7 and you find the one you're looking for on the third shelf. What do you do?
I stop scanning. I got what I came for — no reason to check the other forty shelves.
Right. But what does Python do if you don't tell it to stop?
It keeps going. Checks every remaining item even though you already found what you needed.
That's the gap break fills. Today covers three things that change how loops execute: break stops the loop entirely, continue skips the current item and moves to the next, and the else clause on a loop only runs when the loop finishes without hitting a break. All three fit the same scanner analogy.
The else clause on a loop — not on an if? I didn't know loops had an else.
They do, and it surprises almost everyone the first time. Picture the warehouse scanner. break is "stop scanning, I found what I need." continue is "skip this item, it's damaged, move to the next one." And the loop else is the sign that prints when you finish the whole aisle and never triggered a stop: "Not found in this aisle." It only runs if break never fired.
So if the loop runs all the way through naturally, else executes. If break cuts it short, else is skipped entirely?
Exactly. And this lets you express "I searched everything and found nothing" without a flag variable. Here's the function you're building today:
def find_low_stock(products: list[dict], threshold: int = 10) -> dict | None:
for product in products:
if product["stock"] < threshold:
break
else:
return None
return productreturn product is outside the loop? And product holds the item that triggered the break? Loop variables survive after the loop exits?
They do. Python doesn't clean up loop variables when the loop ends — they hold whatever value they had on the last iteration. When break fires mid-loop, product is the item that triggered it. So returning it directly works.
And if the loop runs to completion without breaking, the else returns None. So the function either returns the first under-threshold product, or None if everything is fine.
No flag variable, no index tracking, no found = False sentinel. The for/else pattern encodes the "did I find it?" question directly in the loop structure.
I've written the found = False version. Multiple times. For the deduplication logic in Track 1.
Nearly everyone does. The flag works — it's just noisier. Here's the side-by-side:
# Flag pattern — works, carries extra weight
def find_low_stock_flag(products: list[dict], threshold: int = 10) -> dict | None:
found = None
for product in products:
if product["stock"] < threshold:
found = product
break
return found
# for/else — same logic, no sentinel variable
def find_low_stock(products: list[dict], threshold: int = 10) -> dict | None:
for product in products:
if product["stock"] < threshold:
break
else:
return None
return productThe second one makes the "not found" case explicit instead of hiding it in a None default. I can read the intent without tracing the flag.
Now continue — the scanner skipping a damaged item. When Python hits continue, it immediately jumps to the next iteration, skipping everything below it in the current pass:
def count_sellable_stock(products: list[dict]) -> int:
total = 0
for product in products:
if product.get("damaged", False):
continue # skip damaged items
if product["stock"] == 0:
continue # out of stock — skip
total += product["stock"]
return totalcontinue doesn't end the loop, it ends this pass. The loop's mission is still running — it just moves to the next item.
Right. And break and continue can appear in the same loop — different triggers, different outcomes. A scanner might skip damaged items with continue but stop entirely when it hits a recalled SKU with break:
QUARANTINE_SKUS = {"SKU-9001", "SKU-9002"}
def scan_for_report(products: list[dict]) -> list[dict]:
report = []
for product in products:
if product["sku"] in QUARANTINE_SKUS:
break # stop everything immediately
if product.get("damaged", False):
continue # skip and keep scanning
report.append(product)
return reportThe quarantine check stops the whole scan — nothing after that point enters the report. A damaged item gets excluded but scanning continues. Two keywords, two genuinely different business rules.
Business rules expressed as control flow — that's what makes code readable to someone who knows the domain, not just someone who knows Python. One edge case worth knowing: break and continue only affect the innermost loop. Nested loops require a different approach.
So if I'm iterating aisles, and inside that iterating shelves, a break inside the shelf loop exits the shelf scan but the aisle loop keeps going?
Exactly. The outer loop doesn't know the inner loop broke. If you need to exit both at once, extract the inner work into a function and use return:
def find_in_warehouse(aisles: list[list[dict]], sku: str) -> dict | None:
for aisle in aisles:
for product in aisle:
if product["sku"] == sku:
return product # exits both loops immediately
return Nonereturn is the real break-out-of-everything. Extracting to a function also gives the logic a name, which makes it readable regardless.
You just described one of the most common refactoring patterns in real Python code. Let's verify one edge case for find_low_stock before we close:
Empty list — the loop body never runs, break never fires, else executes, returns None. That's the correct behavior and it costs nothing to handle. No special-casing required.
Diane is not going to get a crash on an empty inventory batch. That's your whole job.
"Maya Patel: Warehouse Incident Prevention." It doesn't fit on a business card but it's accurate.
Tomorrow: match — structural pattern matching. If today was bending loops to your will, tomorrow is routing data structures to the right handler. That giant if/elif chain you wrote for sales report categories? Tomorrow you finally see what Python was always trying to give you.
I remember that chain. Eight elif blocks. I hated every one of them.
Three loop control statements modify how a for or while loop executes:
break — exits the loop immediately, skipping the else clausecontinue — skips the rest of the current iteration, proceeds to the nextelse on a loop — runs when the loop exhausts its iterable naturally (never triggered if break fires)def find_low_stock(products: list[dict], threshold: int = 10) -> dict | None:
'Return the first product with stock below threshold, or None.'
for product in products:
if product["stock"] < threshold:
break # found one — exit loop, skip else
else:
return None # loop finished without break — nothing found
return product # product holds the item that triggered breakLoop variable survival: Python does not clean up loop variables when the loop exits. After break, the loop variable holds the value from the iteration that triggered the break. This is what makes the pattern work without a temporary variable.
for/else vs flag pattern:
# Flag pattern — works but adds noise
found = None
for p in products:
if p["stock"] < threshold:
found = p
break
return found
# for/else — same logic, no sentinel
for p in products:
if p["stock"] < threshold:
break
else:
return None
return pEdge cases:
else executes immediatelycontinue and break in the same loop: independent statements, different triggersbreak only exits the innermost loop — use return from a helper function to exit nested loopsPitfall 1: Forgetting that else is "no break", not "loop condition false". The loop else runs whenever the loop ends without a break — including when the iterable is empty. For a while loop it runs when the condition becomes False naturally.
Pitfall 2: Expecting break to exit all nested loops. A break inside a nested for only exits the innermost loop. Extract nested search logic into a function and use return to exit cleanly from any depth.
Pitfall 3: Misplacing code after break. Any code between a break and the end of the loop body is unreachable but does not raise an error. Linters flag this; the loop just stops before reaching it.
while loops also support break, continue, and else. The canonical use is a polling loop that breaks when a condition is satisfied and uses else to handle the timeout case:
attempts = 0
while attempts < 5:
result = check_api()
if result.ok:
break
attempts += 1
else:
raise TimeoutError("API did not respond after 5 attempts")
process(result)Python has no labeled break (break 2) — always refactor nested search logic into a named function.