Does a `break` inside a Python `try` block still run the `finally` clause?
answer
- cleanup on the way out
- which clause covers every exit route?
- the jump waits for the cleanup
- finally runs first, then the break
- the try statement's else is skipped
basics
~20 sYes. The finally clause runs whenever control leaves the try statement, including by break, continue or return, and it runs before the jump takes effect. The try statement's own else clause, by contrast, is skipped.
solid answer
~40 s`finally` is attached to every exit edge of the `try` statement, not just the exception path, so a `break` in the `try` suite runs the `finally` body first and only then leaves the loop. The same holds for `continue` and `return`, and for nested `try` statements the cleanups run innermost outwards as control unwinds. A synchronous `with` block behaves the same way — the context manager's `__exit__` runs on the way out — which is why an early `break` never leaks a managed file or lock. One clause does *not* run: the `try` statement's `else` suite executes only when the `try` body finishes normally, and a `break` is not finishing normally. The reverse arrangement is the dangerous one: putting the `break` inside `finally` instead, which Python 3.14 warns about.
code
python · 8 linesfor n in range(3):
try:
if n == 1:
break
finally:
print("finally ran for", n)
print("after the loop")
# finally ran for 0 / finally ran for 1 / after the loopgo deeper
Remember the one-line rule: finally runs on the way out no matter how you leave the try, including a break. That is what makes it the right place for closing and releasing.
Be able to trace the order out loud — finally body first, then the jump — and to say that return and continue behave identically while the try statement's else clause is skipped.
Show the operational side: cleanup on the early-exit path is still on the critical path, per-pass finally bookkeeping also fires for the breaking pass, and nested cleanups unwind innermost outwards.
Set the guidance on where cleanup lives — finally versus a context manager versus an explicit teardown — so exit paths are guaranteed once rather than repeated before every break and return in the codebase.
The guarantee is simple to state and worth stating precisely: **`finally` runs on every path out of its `try` statement.** That includes falling off the end of the suite, an exception propagating, and each of the three jump statements — `break`, `continue` and `return`. Cleanup written in `finally` therefore happens before the loop is abandoned, not after and not never. ### Ordering The `finally` body runs *first*, then the pending jump resumes. A `break` inside a `try` is best read as "stage a break, run the cleanup, then take it". You can see the interleaving directly: a loop that breaks on its second pass still prints from `finally` for that pass before the statement after the loop runs. When `try` statements nest, control unwinds innermost outwards, running each `finally` it passes through. A `break` in a doubly nested `try` inside a loop runs both cleanups, inner first, before the loop ends. The same applies when the jump crosses `with` blocks: each synchronous context manager's `__exit__` runs, in reverse order of entry, with `None` for all three exception arguments because no exception is in flight. That is the mechanism behind the everyday claim that an early `break` cannot leak an open file or a held lock — the guarantee belongs to the statement, not to the loop. ### The clause that does *not* run The `try` statement's own `else` suite is the exception. It runs only when the `try` body completes without raising *and* without jumping — so a `break` skips it while still running `finally`. This trips people up because both clauses feel like "the happy path". They are not: `else` means "the body finished", `finally` means "control is leaving". Note that this is the `else` belonging to `try`, which is a different clause from the one a `for` or `while` loop can carry. ### Why it is designed this way Without the guarantee, `finally` would be useless: any early exit would be a way to skip cleanup, and every resource release would need to be repeated before every `break` and `return`. Attaching the cleanup to the statement rather than to the exit route is what lets you write the release exactly once. The compiler implements this by routing each exit through the `finally` code before the jump completes, which is why the cost is paid even on the ordinary fall-through path. ### Where it bites in practice A `finally` that runs per pass runs on the `break` pass too, so anything counted there — a metric increment, a log line, a release of a per-row resource — fires for the row that triggered the exit as well. When a reconciliation loop over a 6,800-row batch breaks on the first row whose recomputed amount drifts from the reported one, the `finally` bookkeeping for that row has already run; a total that looks off by exactly one row is usually this, not a real accounting bug. The other practical consequence is timing. A `finally` performing slow cleanup delays the `break` for as long as it takes, and a `finally` that blocks forever means the loop never exits at all. Cleanup on the early-exit path is still on the critical path. ### The dangerous inverse Everything above concerns a jump inside the `try` (or `except`) suite. Putting the jump inside `finally` itself is a different construct with a nasty semantics: control leaving a `finally` block by `break`, `continue` or `return` discards whatever was in flight — including an exception that was propagating — so the error simply vanishes. Python 3.14 (PEP 765) emits a `SyntaxWarning` for exactly that shape. `continue` inside `finally` was in fact a `SyntaxError` before Python 3.8. ### Answering well Say "yes, and it runs before the break takes effect", then add the two refinements that show real understanding: `return` and `continue` behave identically, and the `try` statement's `else` clause is skipped where `finally` is not. If you have room, note that `with` gives the same guarantee through `__exit__`, and that the reverse arrangement — a `break` inside `finally` — is the one to avoid.
- Does an early `break` out of a `with` block still release the context manager?Yes. Leaving the `with` statement by any route calls the synchronous context manager's `__exit__`, and on a `break` it is called with `None` for the exception type, value and traceback because nothing is propagating. Nested `with` blocks unwind in reverse order of entry. That is why a file opened in a `with` inside a loop is closed on the breaking pass, without any explicit close before the `break`.
- Why does a `break` in the `try` suite skip that statement's `else` clause but not its `finally`?They answer different questions. The `try` statement's `else` runs only when the body completed normally — no exception and no jump — so a `break` disqualifies it. `finally` runs whenever control leaves the statement at all, which a `break` certainly does. Reading them as "body succeeded" versus "control is leaving" makes the difference obvious and stops people putting cleanup in `else`.
- If a slow cleanup sits in `finally`, does it delay the `break`?Yes. The `finally` body runs to completion before the jump resumes, so the loop exits only once cleanup is done, and a `finally` that blocks indefinitely means the loop never exits. Early-exit paths are not shortcuts around cleanup cost. If the cleanup is genuinely expensive and not required for correctness on that path, it does not belong in `finally` for every pass.
saying these in an interview costs you the question
- Claims break skips finally as a fast exit
- Thinks finally runs only for exceptions
- Says the jump happens before finally runs
- Expects the try statement's else to run on break
- Believes an early break leaks an open file
- Puts break inside finally to force an exit