An exception inside a submitted task is stored on its Future. Calling result() raises it in the collecting thread; waiting for the executor to finish does not itself report every task failure.
Check the failure with a small fixture
A submitted task fails without a traceback in the caller. Collect outcomes explicitly rather than treating submit as success.
from concurrent.futures import ThreadPoolExecutor, as_completed
def run_job(n):
if n == 2:
raise ValueError("fixture failure")
return n * 10
successes, failures = {}, {}
with ThreadPoolExecutor(max_workers=2) as pool:
jobs = {pool.submit(run_job, n): n for n in [1, 2, 3]}
for future in as_completed(jobs):
n = jobs[future]
try:
successes[n] = future.result()
except Exception as exc:
failures[n] = type(exc).__name__
assert successes == {1: 10, 3: 30}
assert failures == {2: "ValueError"}
print(successes, failures)What the result means
The fixture associates each Future with its input before collecting completions. Completion order is not submission order; the dictionaries preserve the task identity needed to diagnose a specific failure. The context manager waits for pending work during shutdown, but the explicit result calls are where failures become caller-visible.
Choose the correction for your caller
Choose whether your batch should collect all outcomes or stop accepting new work after a failure. Log the job identifier and exception type using your application policy. If you use executor.map, consume its iterator: a task exception is raised as its result is retrieved. If iteration stops at a failure, deliberately account for the other tasks rather than assuming they were cancelled. Future.exception() can inspect a finished failure without raising that task exception, but it is still an explicit outcome read.
Limits and the next check
Catching Exception here is deliberate batch collection, not a recommendation to discard failures. Cancellation is cooperative and cannot undo side effects already performed by running tasks. The example uses threads and pure local functions, not process serialization or asyncio task semantics.
Related: Compare a related data or execution contract.
The embedded Python example was executed as an isolated local script on November 10, 2026 (KST), using Python 3.11.6. It demonstrates the stated boundary; it is not a test of your application or a promise of compatibility with every Python release.