Skip to the solution
MediumDSA13 min read

How does exception propagation work up the call stack?

773 views

The problem

Question presented to candidate: "A function four calls deep raises a ValueError, and only the top-level function has a try/except. Walk me through exactly what Python does between the raise and the handler. What runs on the way up, and where can the exception get stopped or lost?"

What a strong answer should cover:

  • Raising stops the current frame; Python looks for a matching handler in that frame, and if there is none the frame is discarded and the search continues in the caller, frame by frame (stack unwinding)
  • On the way up every finally block and every with block exit (__exit__) runs; except clauses that do not match are skipped
  • The traceback is built as the exception passes each frame: a linked list of traceback objects (tb_frame, tb_lineno, tb_next) on e.__traceback__, outermost frame first
  • Propagation stops at a matching except, at a context manager whose __exit__ returns true (for example contextlib.suppress), or at the top level, where the interpreter prints the traceback and exits with status 1
  • Bare raise keeps the traceback unchanged while raise e adds the current line; raising inside a handler replaces the exception (keeping it as __context__)
  • Exceptions do not cross thread boundaries on their own: an uncaught error in a thread goes to threading.excepthook; Future.result() re-raises it in the caller

Clarifying questions expected:

  • Is any of this running in a thread, a generator or an async task? (Each changes who receives the exception.)

Code / implementation expected: Optional โ€” a chain of three or four functions with finally blocks and one handler at the top, printing the order of events.

  • exceptions
  • propagation

Try it yourself

Sketch your approach before you read the solution. That is what the interview tests.

Stuck? Get a hint.

A short nudge toward the approach. It will not give the answer away.

Solution

The solution is hidden so you can try first

13 min read ยท 11 sections

Run the code

Edit the code and run it here. Each console line with an expected value becomes a test.

PythonUnwinding four frames: finally, skipped clauses and the traceback chain
PythonPropagation through sorted(), generators and context managers
PythonUncaught errors, threads and futures (needs real processes and threads)
import subprocess
import sys
import threading

# 1. Nobody catches it: the interpreter prints the traceback and exits with 1.
child = subprocess.run(
    [sys.executable, "-c", "def f():\n    1 / 0\nf()\nprint('never')"],
    capture_output=True,
    text=True,
)
print("exit code:", child.returncode)
print("stdout:", repr(child.stdout))
print("last stderr line:", child.stderr.strip().splitlines()[-1])
print("traceback header:", child.stderr.splitlines()[0])

# 2. Exceptions do not cross threads: the worker dies, main carries on.
seen = []

def hook(args):
    seen.append((args.thread.name, args.exc_type.__name__, str(args.exc_value)))

threading.excepthook = hook

def worker():
    raise ValueError("bad row 17")

t = threading.Thread(target=worker, name="loader")
t.start()
t.join()
print("main thread still running; hook saw:", seen)

# 3. To get it back in the caller, a Future re-raises it on .result().
from concurrent.futures import ThreadPoolExecutor

with ThreadPoolExecutor(max_workers=1) as pool:
    future = pool.submit(worker)
    try:
        future.result()
    except ValueError as e:
        print("re-raised in main by future.result():", e)

Discussion

Other approaches, follow-ups and how it went in real interviews.

Sign in to join the discussion.

No comments yet. Share how you would answer it.

Question 67 of 139 in Python

Keep your momentum going.

Mark this question solved to track your progress, then take the next one while it is fresh.

All Python questions
Up nextMedium

When do you use threading, multiprocessing, or asyncio?

Continue