You already know how to decide with if and repeat with for and while, but your loops are still rigid: they traverse the whole sequence no matter what. In this lesson you'll learn to take fine-grained control of execution: break to exit a loop as soon as you find what you're looking for, continue to skip elements, pass as a placeholder, the little-known else clause of loops, the while True pattern for menus that repeat until the user decides to leave, and the modern match/case statement from Python 3.10. With all of them we'll build the first interactive Papyrus menu, the skeleton on which Ana's application will grow for the rest of the course.

Contents

  1. break: exiting the loop early
  2. continue: skipping to the next iteration
  3. pass: the statement that does nothing
  4. The else clause on loops
  5. Controlled infinite loops: while True + break
  6. match/case: pattern-based selection (Python 3.10+)
  7. Project: the interactive Papyrus menu

break: exiting the loop early

The break statement ends the loop immediately: Python jumps to the first statement after the loop, without completing the current pass or doing the remaining ones.

The quintessential use case is searching: Luis asks whether Papyrus has Hamlet. As soon as we find it, there's no point checking the rest of the catalog.

catalog = ["The Odyssey", "Hamlet", "Don Quixote"]
wanted = "Hamlet"

for title in catalog:
    print(f"Checking '{title}'...")
    if title == wanted:
        print(f"Found it! '{wanted}' is in the catalog.")
        break

print("Search finished.")

Output:

Checking 'The Odyssey'...
Checking 'Hamlet'...
Found it! 'Hamlet' is in the catalog.
Search finished.

Notice: Don Quixote is never checked. With a 3-book catalog it makes no difference, but with 30,000 the difference is enormous.

Important details about break:

  • It can only be used inside a loop (for or while); outside one it produces a SyntaxError.
  • In nested loops, break exits only the innermost loop that contains it, not all of them.
  • It almost always appears inside an if: "if this happens, stop iterating".

continue: skipping to the next iteration

While break abandons the loop, continue abandons only the current pass: it jumps straight to the next iteration, without running the rest of the block.

Ana wants to print the list of sellable books, skipping the ones that are out of stock:

catalog = ["The Odyssey", "Hamlet", "Don Quixote", "Faust"]
stocks = [4, 6, 8, 0]

print("Books available for sale:")
for i, title in enumerate(catalog):
    if stocks[i] == 0:
        continue    # out of stock: move on to the next without printing anything
    print(f"- {title} ({stocks[i]} units)")

Output:

Books available for sale:
- The Odyssey (4 units)
- Hamlet (6 units)
- Don Quixote (8 units)

Faust (newly arrived in the catalog, but with stock 0) is skipped cleanly. The pattern if discard_condition: continue at the start of the block is sometimes called a "guard clause" and has a big readability advantage: the loop's "normal" case stays free of extra indentation, instead of wrapping everything in a giant if.

A quick comparison of the two statements:

Statement What it interrupts The loop... Typical use
break The whole loop Ends right away "Found it, I stop searching"
continue Only the current pass Carries on with the next "This element doesn't interest me, next"
flowchart TD
    A[Next element] --> B{discard?}
    B -- "continue" --> A
    B -- No --> C[Process element]
    C --> D{goal reached?}
    D -- "break" --> E[Exit the loop]
    D -- No --> A

pass: the statement that does nothing

pass is exactly that: an empty statement. It exists because Python's syntax requires every indented block to contain at least one statement; when you don't yet know what to put there (or deliberately want to do nothing), pass fills the gap with no effects.

choice = "3"

if choice == "1":
    print("Showing catalog...")
elif choice == "2":
    pass    # TODO: book search, we'll implement it further down
else:
    print("Unrecognized option.")

Without the pass, that elif with an empty body would raise an IndentationError. Its usual uses:

  • Skeletons: writing the program's structure first (branches, loops, and later functions and classes) and filling in the blocks bit by bit.
  • Deliberately ignoring cases, leaving an explicit record that it isn't an oversight.

Don't confuse pass with continue: inside a loop, pass skips nothing (execution carries on at the next line); it simply "does nothing" and the normal flow continues.

The else clause on loops

Here comes a very Python-specific quirk: for and while loops can take an else block. Its meaning is surprising at first: the else runs if the loop finished naturally, without break.

What is it for? For the search pattern we saw earlier: what happens if the book is not there? Without else we'd need a flag variable:

# Flag version (works, but more noise)
catalog = ["The Odyssey", "Hamlet", "Don Quixote"]
wanted = "Faust"
found = False

for title in catalog:
    if title == wanted:
        found = True
        break

if found:
    print(f"'{wanted}' is in the catalog.")
else:
    print(f"'{wanted}' isn't here. Ana can order it from the distributor.")

With the loop's else, the flag disappears:

# for/else version (idiomatic in Python)
catalog = ["The Odyssey", "Hamlet", "Don Quixote"]
wanted = "Faust"

for title in catalog:
    if title == wanted:
        print(f"'{wanted}' is in the catalog.")
        break
else:
    print(f"'{wanted}' isn't here. Ana can order it from the distributor.")

Output:

'Faust' isn't here. Ana can order it from the distributor.

How to read it without getting confused: think of the loop's else as an "if there was no break" (some call it the nobreak clause). The exact flow:

  • The loop goes through all the elements without a break running → when it finishes, the else runs.
  • A break runs on any pass → the else is skipped.

Two warnings:

  • The else is aligned with the for/while, not with any inner if. An indentation slip changes the meaning completely.
  • It's a little-known construct even among experienced programmers; use it for searches (its star use case) and add a comment if you think whoever reads the code might not know it.

It also works with while: the else runs when the condition becomes false (natural exit), but not if you leave with break.

Controlled infinite loops: while True + break

In the previous lesson the infinite loop was a mistake to avoid. But there's a deliberate and controlled version that is a fundamental pattern: while True with a break inside. It's used when the program must repeat something indefinitely until the user decides to stop — exactly what any menu, cash machine or interactive application does.

while True:
    answer = input("Type 'quit' to finish: ")
    if answer == "quit":
        print("See you soon!")
        break
    print(f"You typed: {answer}")

Since True is always true, the loop never ends because of its condition: the only way out is the break. This isn't an accidental infinite loop, because the exit exists and is clear; the key is to guarantee that the break is reachable.

This pattern also fully solves the input validation we left half-done in book_card.py:

while True:
    text = input("Initial stock for the book: ")
    if text.isdigit():           # does the string contain only digits?
        stock = int(text)
        break
    print("Invalid input. Type a whole number, for example: 4")

print(f"Stock recorded: {stock}")

isdigit() is a string method that returns True if all its characters are digits (we'll see it along with the rest of the str methods in module 4; for now, use it as is). The loop insists until it receives a valid number, and only then converts and exits. Compared to the while answer != ... from the previous lesson, this form avoids duplicating the input() or initializing artificial variables: that's why it's the preferred one in Python.

match/case: pattern-based selection (Python 3.10+)

When a program must react differently depending on the specific value of a variable (a menu option, a status code, a customer type), a chain of if/elif works, but since Python 3.10 there's a more expressive alternative: the match statement.

choice = "2"

match choice:
    case "1":
        print("Showing the full catalog...")
    case "2":
        print("Book search...")
    case "3":
        print("Exiting Papyrus. See you tomorrow, Ana!")
    case _:
        print("Invalid option.")

How it works:

  • match expression: evaluates the expression once.
  • Each case pattern: is compared in order; the first one that matches runs its block and the statement ends (there's no "fall-through" to the next case, unlike the switch in other languages: you don't need any break here).
  • case _: is the wildcard: it matches anything, and acts as the final else. It must go last.
  • If no case matches and there's no wildcard, simply nothing runs (no error).

Besides literal values, the basic patterns allow two very useful things:

Alternatives with | (the vertical bar): one case for several values.

answer = input("Confirm the sale? (y/n): ")

match answer:
    case "y" | "Y" | "yes":
        print("Sale confirmed.")
    case "n" | "N" | "no":
        print("Sale cancelled.")
    case _:
        print("Unrecognized answer.")

An extra condition with if (called a guard): the case only matches if the condition also holds.

stock = 2

match stock:
    case 0:
        print("Out of stock.")
    case n if n <= 3:
        print(f"Low stock: {n} left.")
    case n:
        print(f"Stock OK: {n} units.")

In case n if n <= 3:, the pattern n is a capture pattern: it matches any value and stores it in the variable n, but the guard if n <= 3 filters when it's accepted. It's a gateway to match's more advanced structural patterns (unpacking lists, dictionaries or objects), which will make sense once you know those structures in modules 4 and 5.

match or if/elif? A quick guide:

Situation Recommendation
Comparing the same variable against specific values (menus, codes) match wins on clarity
Heterogeneous conditions (stock > 0 and is_member, ranges, several pieces of data) if/elif
Your code must run on Python < 3.10 if/elif, no choice

Project: the interactive Papyrus menu

Let's bring all the lesson's pieces together in a new script for the project: menu.py, inside the papyrus folder (with the .venv activated, as always). It's the first Papyrus program that converses with Ana: it repeats until she decides to exit.

STORE_NAME = "Papyrus"
catalog = ["The Odyssey", "Hamlet", "Don Quixote"]
prices = [12.50, 9.95, 15.90]
stocks = [4, 6, 8]

print(f"Welcome to the {STORE_NAME} management system")

while True:
    print()
    print("1. View catalog")
    print("2. Search for a book")
    print("3. Exit")
    choice = input("Choose an option (1-3): ")

    match choice:
        case "1":
            print(f"\n{'Title':<12} {'Price':>10} {'Stock':>6}")
            print("-" * 30)
            for i, title in enumerate(catalog):
                print(f"{title:<12} {prices[i]:>6.2f} EUR {stocks[i]:>6}")
        case "2":
            wanted = input("Title to search for: ")
            if not wanted:
                print("You didn't type anything.")
                continue
            for i, title in enumerate(catalog):
                if title.lower() == wanted.lower():
                    print(f"'{title}': {prices[i]:.2f} EUR, stock {stocks[i]}.")
                    break
            else:
                print(f"'{wanted}' is not in the catalog.")
        case "3":
            print(f"Closing {STORE_NAME}. See you tomorrow, Ana!")
            break
        case _:
            print("Invalid option. Choose 1, 2 or 3.")

Go over how each tool plays its part:

  1. while True keeps the menu alive: after each operation, it's shown again.
  2. match choice dispatches each option with a clean case, and case _ catches typing mistakes.
  3. In the search, continue acts as a guard if Ana presses Enter without typing (truthiness of the empty string, as in lesson 02-01): it returns to the menu without running the search.
  4. for/break/else: the break cuts out as soon as there's a match, and the loop's else covers the "not found" case without flag variables. (lower() converts the text to lowercase so the search isn't case-sensitive; more on string methods in module 4.)
  5. break in case "3" is the only way out of the while True: note that it breaks the outer loop because match is not a loop — break and continue pass straight through the match statement and act on the loop that contains it.

A sample session:

Welcome to the Papyrus management system

1. View catalog
2. Search for a book
3. Exit
Choose an option (1-3): 2
Title to search for: hamlet
'Hamlet': 9.95 EUR, stock 6.

1. View catalog
2. Search for a book
3. Exit
Choose an option (1-3): 3
Closing Papyrus. See you tomorrow, Ana!

This menu is deliberately simple: in module 3 we'll reorganize it with functions (one per option) and in module 6 it will let the catalog survive between sessions by saving it to files. The skeleton, however, won't change anymore: while True + match + break is the standard pattern for console applications.

Common Mistakes and Tips

  • Expecting break to exit all nested loops: it only exits the innermost one. If you need to leave several levels, it's usually a sign that the code deserves to be a function (module 3), where return exits everything at once.
  • Misreading the loop's else as "if the loop never ran": false. With an empty sequence, the loop ends without a break... and the else does run. Remember: else = "there was no break".
  • Using break or continue outside a loop (for example, inside a standalone if, or expecting them to cut a match short): SyntaxError. They only make sense inside for/while.
  • Overusing continue: several guards in a row at the top of the loop are readable; continues scattered through the middle of the block make the flow hard to follow. If that happens to you, rethink the conditions.
  • Forgetting the exit break in a while True: then you really do have an infinite loop. When you write while True:, write the exit case/if with its break before filling in the rest.
  • Writing case "1" | "2": when each value needs different logic: the | alternative is for cases that share a block. If the blocks differ, they're separate cases.
  • Using match with old Python: on versions earlier than 3.10 it gives a SyntaxError. Check your version with python --version; if you maintain code for old versions, use if/elif.
  • Tip: pass is useful while you're building, but it shouldn't make it into the script's final version unless ignoring the case is a conscious decision — and in that case, comment it.

Exercises

Exercise 1: first sold-out order

Given pending_sales = [2, 1, 3, 2, 4] (units requested by several customers, in order) and a stock = 6 of Hamlet, go through the orders with a for loop: fulfil each order by subtracting from the stock and print Order of N units fulfilled (M left). If an order can't be fulfilled in full, print Not enough stock for the order of N units and stop processing orders at that point. If all were fulfilled, print All orders fulfilled at the end (hint: for/else).

Exercise 2: report skipping discontinued titles

Given the lists titles = ["The Odyssey", "Hamlet", "Don Quixote", "Faust"] and statuses = ["active", "active", "discontinued", "active"], print a numbered report (starting at 1) of only the active books, using continue to skip the discontinued ones. The numbering must be consecutive for the ones shown (1, 2, 3), not their original position.

Exercise 3: Papyrus cash register with match

Write a while True loop that asks Ana for an operation code and reacts with match:

  • "s" or "sale" → ask for an amount and add it to a till accumulator (start from till = 0.0).
  • "r" or "refund" → ask for an amount and subtract it from till.
  • "t" → print the current total with two decimals.
  • "x" → print the final total and exit.
  • Anything else → an error message.

Solutions

Solution 1:

pending_sales = [2, 1, 3, 2, 4]
stock = 6

for order in pending_sales:
    if order > stock:
        print(f"Not enough stock for the order of {order} units")
        break
    stock -= order
    print(f"Order of {order} units fulfilled ({stock} left)")
else:
    print("All orders fulfilled")

With stock 6, the orders for 2 and 1 are fulfilled (3 left), then the one for 3 (0 left), and the one for 2 can no longer be fulfilled: break. Since there was a break, the else doesn't run — exactly what we want. Try changing stock = 12 and you'll see the final message.

Solution 2:

titles = ["The Odyssey", "Hamlet", "Don Quixote", "Faust"]
statuses = ["active", "active", "discontinued", "active"]

number = 0
for i, title in enumerate(titles):
    if statuses[i] == "discontinued":
        continue
    number += 1
    print(f"{number}. {title}")

The key is keeping a counter of our own (number) that only increments after the guard with continue: that way the discontinued titles don't consume a number and the list shown reads 1. The Odyssey, 2. Hamlet, 3. Faust.

Solution 3:

till = 0.0

while True:
    code = input("Operation (s/r/t/x): ")
    match code:
        case "s" | "sale":
            till += float(input("Sale amount: "))
        case "r" | "refund":
            till -= float(input("Refund amount: "))
        case "t":
            print(f"Till total: {till:.2f} EUR")
        case "x":
            print(f"Closing. Final total: {till:.2f} EUR")
            break
        case _:
            print("Unrecognized code. Use s, r, t or x.")

The lesson's full pattern in miniature: while True as the program's heart, | to accept synonyms, case _ for typing mistakes, and a single break as the exit. As always, float() will fail if Ana types text for the amount — the safety net arrives in module 7.

Conclusion

Your loops are no longer trains without brakes: with break you stop as soon as you find what you're looking for, with continue you skip what doesn't interest you, with pass you reserve the spot for what's yet to be written, with the loops' else you detect unsuccessful searches without flag variables, and with while True + break you've built the universal pattern of interactive programs. On top of that, match/case gives you a modern, readable way to dispatch options. It all came together in menu.py, the first Papyrus program that talks with Ana and the skeleton of everything to come.

There's one last superpower left in this module. You'll have noticed that many of our loops follow the same pattern: traversing one list to build another (prices with VAT, titles filtered by stock...). Python has a specific, compact and supremely elegant syntax for that pattern: list comprehensions. They're the hallmark of Pythonic code, and with them we'll close the module in the next lesson.

Python Programming Course

Module 1: Introduction to Python

Module 2: Control Structures

Module 3: Functions and Modules

Module 4: Data Structures

Module 5: Object-Oriented Programming

Module 6: File Handling

Module 7: Error and Exception Handling

Module 8: Advanced Topics

Module 9: Testing and Debugging

Module 10: Web Development with Python

Module 11: Data Science with Python

Module 12: Final Project

© Copyright 2026. All rights reserved