What do the else and finally clauses of a try block do, and when does finally NOT run?
The else clause runs only when the try block exits without raising an exception — it lets you separate success-path code from the guarded block. The finally clause runs in every case: after normal exit, after an exception (caught or uncaught), and after a return or break inside try or except. The only situations where finally is skipped are a hard interpreter crash (SIGKILL, os._exit, power loss).
How to think about it
Almost everyone knows try/except. The follow-up on else and finally checks whether you understand the execution model precisely — particularly the quiet ways finally interacts with return and break. Getting it right signals you write code for the unhappy path too, not just the happy one.
The four clauses each have a distinct job:
try:
result = risky_call() # the guarded code
except ValueError as e:
handle(e) # a specific exception
else:
process(result) # ONLY if try raised nothing
finally:
cleanup() # ALWAYS runs
else is the subtle one. Without it you’d be tempted to drop process(result) inside the try — but then any exception process itself raises would be swallowed by the except handlers above, almost never what you want. else cleanly separates “protected code” from “what to do once it succeeded.”
A worked example
The clearest way to see each clause fire is to print from inside one and run it down both paths — plus a third case proving finally runs even around a return:
def divide(a, b):
try:
result = a / b
except ZeroDivisionError as e:
print(f" except: caught {e}")
result = None
else:
print(f" else: no exception, result = {result}")
finally:
print(f" finally: always runs")
return result
print("Case 1 — success path:")
divide(10, 2)
print()
print("Case 2 — exception path:")
divide(10, 0)
print()
# finally runs even when try returns early
def early_return():
try:
print(" try: about to return")
return 42
finally:
print(" finally: runs BEFORE the return happens")
print("Case 3 — finally survives return:")
print(f" caller got: {early_return()}")
Case 1 — success path:
else: no exception, result = 5.0
finally: always runs
Case 2 — exception path:
except: caught division by zero
finally: always runs
Case 3 — finally survives return:
try: about to return
finally: runs BEFORE the return happens
caller got: 42
Read down the three cases. On success, else ran (and except didn’t); on the divide-by-zero, except ran (and else didn’t); and in both, finally ran. Case 3 is the one that surprises people: the try block hit return 42, yet finally still printed before the function actually returned. finally truly means always — it fires before a return, a break, or a continue can carry control away.
When finally genuinely does not run
The guarantee has only a few hard exceptions, all outside Python’s normal control flow:
os._exit()— bypasses the interpreter’s shutdown entirely;- a
SIGKILLfrom outside — the OS tears the process down without unwinding; - a C-level segfault in an extension — the interpreter dies before its exception machinery runs.
The mental model
Read it as plain English: try this, except handle errors, else do this on success, finally always clean up. Keeping those four concerns in four clauses is what makes the control flow obvious to the next reviewer.