Skip to content
datarekha
Python Medium Asked at GoogleAsked at StripeAsked at Atlassian

What do the else and finally clauses of a try block do, and when does finally NOT run?

The short answer

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 SIGKILL from 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.

Learn it properly Errors & Exceptions

Keep practising

All Python questions

Explore further