break, continue, and pass are three small keywords that change how your loops and code blocks behave. break walks out of a loop early, continue skips ahead to the next iteration, and pass does nothing at all. They look trivial, but each one solves a specific problem cleanly, and each has a sharp edge or two that's worth knowing before you write code that depends on them.
break: Exit the Loop EarlyYou'll reach for break whenever you don't need to finish the loop. Maybe you found the item you were searching for. Maybe an error condition came up and continuing would be wasteful or wrong. Maybe you only wanted to process the first N items and the rest can be ignored.
The classic case is searching: scan a cart for a specific product, and stop the moment you find it.
Without break, the loop would keep walking through "Webcam" even though we already had the answer. For a four-item cart that's invisible. For a list of ten thousand items, scanning past the match is wasted work on every iteration after the hit.
break jumps to the first statement after the loop. It doesn't run any remaining body for the current iteration, and it doesn't run the rest of the loop. Control flow drops straight out.
Here's the same idea in a slightly larger setting: stop processing on the first invalid item.
The loop adds the first two prices, hits the negative one, prints a message, and breaks out. The fourth price (14.99) is never seen. The variable valid_total keeps whatever value it had at the moment of the break, which is exactly what you want for a "stop on first error" pattern.
A third pattern is "process up to N items": you stop deliberately after a counted number of iterations.
This works fine, though for a fixed count you'd usually slice the list (recently_viewed[:3]) or use itertools.islice. Reach for break when the stopping condition is dynamic and depends on what you've seen along the way.
break works the same way in a while loop. It exits the loop immediately, regardless of whether the loop condition would still be true.
The while True pattern is common when the natural exit condition is easier to express inside the loop body than as a loop header. The break is the actual exit; without it, you'd have an infinite loop.
continue: Skip to the Next Iterationcontinue is the opposite move. Instead of leaving the loop, it abandons the rest of the current iteration and jumps back to the top to start the next one. In a for loop, that means moving to the next item from the iterable. In a while loop, it means re-testing the loop condition.
The most common use is filtering inside a loop: skip items that don't qualify, process the ones that do.
Free items (0) and the bad data (-5.00) get skipped. Every valid price is added. The loop never exits early; it sees every item, but only acts on some of them.
Another fit: skip out-of-stock items while you build a display list.
Two products get skipped, two get appended. The flow reads naturally: "for each product, if it's out of stock, move on; otherwise add it to the list."
Here's a counting case: count how many full-price items are in a cart, ignoring discounted ones.
Two of the four items are discounted, so they're skipped. The other two each bump the counter.
Here's how break and continue differ at the level of control flow. The diagram traces a single iteration and shows where each keyword sends you.
continue loops back to the top to ask "any more items?". break skips the question entirely and lands on whatever comes after the loop. Plain "finish iteration" is what happens when you reach the end of the loop body without hitting either keyword: the iteration completes normally, then the loop asks for the next item.
A subtle point about while loops: continue jumps straight to the loop's condition test, which means anything you'd normally do at the bottom of the body, like incrementing a counter, gets skipped if it sits below the continue.
What's wrong with this code?
The program prints 0 and 1, then hangs forever. When i becomes 2, continue jumps back to the condition without running i += 1, so i stays at 2 and the same continue fires on every loop forever.
Fix: Update the counter before the continue, or use a for loop where the counter is managed for you:
In a for loop, the iterator advances on its own when control returns to the top, so continue is safe by default. In a while loop, you have to be careful that the loop variable still moves toward the exit condition before any continue.
Cost: Neither break nor continue saves work in any magical way. They just change which lines of the loop body run and when the loop ends. The savings come from skipping work you would otherwise do, not from the keyword itself.
break and continue Only Affect the Innermost LoopThis is the trap. break and continue always act on the loop they sit directly inside. They don't escape outer loops. People forget this, and end up with code that "almost works" until the data hits a case that exposes the bug.
Picture a refund job that processes orders, where each order has multiple items. The intent is "stop everything as soon as we hit one bad item." Here's the wrong way:
The reader expected the program to stop entirely at the bad amount, with processed equal to 5 (three items from the first order, then the two clean items from the second before the -1.00). Instead it kept going. The break only exited the inner loop, so the outer loop happily moved on to the third order and processed two more items.
There are two clean ways to fix this.
Fix 1: Use a flag variable. Set a flag when the inner loop wants to abort, and check it in the outer loop.
Now both loops exit. The flag is the bridge: the inner break exits the inner loop, the outer if abort: break exits the outer one. It works, but the bookkeeping is noisy. Two breaks, one flag, and the reader has to scan both loops to be sure of the flow.
Fix 2: Extract the inner loop into a function and use `return`. A return statement exits the function entirely, no matter how many loops it's nested inside.
This version is shorter, has no flag variable, and reads top-to-bottom. The function-extraction trick is the standard Python answer to "I need to break out of nested loops." Most code that uses a flag for this would read better as a function.
The same rule applies to continue. An inner-loop continue only restarts the inner loop. If you want to skip to the next outer iteration, you need to structure your loops or your function differently.
Here we replaced the inner loop with a single any(...) call so there's no nesting at all, and the outer continue does exactly what we want. Pulling the inner check into a separate expression or function is often a better fix than reaching for nested break and continue.
pass: A Statement That Does Nothingpass is a no-op. It runs, takes no time worth measuring, and produces no effect. The reason it exists is purely syntactic: Python requires a non-empty body after if, def, class, for, while, try, and similar statements. If you have nothing to put there, you need something the parser will accept. That's pass.
The most common use is a stub: a class or function you've sketched the signature for but haven't implemented yet.
Both Order and cancel_order are completely empty, but Python accepts them. Order() creates an instance with no attributes or methods of its own. cancel_order(42) runs the empty body and returns None, which is what every function returns when it doesn't explicitly return anything.
Without pass, leaving the body empty is a SyntaxError:
Python needs something indented under class Order:. pass fills that gap with a statement that does nothing observable.
The same pattern works inside if branches. Sometimes the rule you're modeling really has nothing to do in one of its cases, and a pass makes that explicit:
That pass says "if the order is cancelled, do nothing here, on purpose." It's clearer than restructuring the if to invert the condition, especially if you expect to fill in real behavior later.
pass also shows up in try/except blocks where you genuinely want to swallow a specific error. Here's a careful version that only ignores the error type you're prepared for:
The int("abc") call raises ValueError, the except catches it, and pass says "carry on." The bad input gets dropped, the good values are collected. This is a legitimate use, and it's narrow: we caught one specific error type, which is the one we expected.
Which brings us to a sharp edge.
What's wrong with this code?
The output is the same, but the except: (with no exception type) catches everything: not just ValueError, but KeyboardInterrupt (the user hit Ctrl+C), MemoryError, programming bugs like NameError, and anything else that gets raised. Combined with pass, that means errors disappear silently with no log, no print, no chance to debug. A typo in the loop body becomes invisible.
Fix: Always name the exception types you actually expect to handle.
If you genuinely want to log and continue, log first, then pass (or just don't bother with pass, since logging counts as a real statement and the except body is no longer empty).
Cost: pass compiles to a NOP in Python's bytecode and has no measurable runtime cost. It's purely a parser convenience.
pass vs ... (the Ellipsis Literal)Python has a second placeholder that does almost the same job: ..., the Ellipsis literal. Like pass, it's a valid statement on its own, so it satisfies the parser when a body is required.
Both work identically here. The differences are in convention and in what they actually evaluate to.
| Marker | What it is | Where it's idiomatic |
|---|---|---|
pass | A statement that does nothing | Empty bodies you don't intend to fill in (real no-op if branches, except: pass) |
... | The Ellipsis object, used as a one-line statement | Stub bodies you plan to implement later, type-stub files (.pyi), abstract methods |
A common convention in modern Python code: ... flags "this is a placeholder I haven't implemented yet," while pass flags "this body is intentionally empty." Both are valid in either spot, and Python doesn't care, but readers of your code might.
... also has uses outside of stubs (it shows up in NumPy slicing, for example), while pass is a pure statement with no value. If you find yourself in doubt, pass is the canonical choice for empty bodies, and it's the version every Python programmer recognizes immediately.
A few traps come up enough to mention them explicitly.
Mistake 1: Assuming `break` exits all loops. Covered above. break exits exactly one loop, the innermost one it sits in. If you need to leave nested loops, use a flag or refactor into a function with return.
Mistake 2: Reaching for `continue` when an inverted condition would be clearer. continue is great when there are several skip cases or when the "do the work" branch is long. For a single skip, an inverted if often reads better.
Both versions produce the same result. The continue form is fine, but for a single check, the if price != 0 form is shorter and avoids introducing a control-flow keyword. Save continue for cases where the body after it is long, or where you have several different skip conditions to filter on.
Mistake 3: Using `pass` where a comment or `raise NotImplementedError` would communicate intent better. A bare pass in a stub function silently returns None, which can hide bugs. If a function isn't implemented yet, raising an error makes that obvious the moment something tries to call it.
That traceback tells you exactly what's missing. A pass would have silently returned None and let the rest of your code limp along on bad data. Use pass when the empty body is intentional (a real no-op branch, an expected error to swallow), and use raise NotImplementedError (or ... plus a TODO comment) for "I haven't gotten to this yet."
Mistake 4: `continue` in a `while` loop above the variable update. The earlier "infinite loop" example. In a while loop, anything you continue past is skipped, including the lines that move the loop toward its exit. Either move the update above the continue, or restructure as a for loop.
10 quizzes