LESSON 155 · PYTHON TOOLS30 min

Python concurrent.futures as_completed: Handle Results as They Finish

Consume futures in completion order, associate each result with its input, and handle individual failures.

Example check: Worked and guided code samples are checked against CPython 3.13. Practice uses Pyodide 0.29.5 (Python 3.13) in your browser. How we check lessons

Video lesson: Python concurrent.futures as_completed: Handle Results as They Finish

The preview is stored on this site. YouTube loads only when you press Play. Watch on YouTube

Read the video transcript

Select a timestamp to watch that moment on YouTube.

  1. 00:00

    Use concurrent.futures.as_completed(futures) when you want to react as submitted tasks finish rather than wait for their original input order. submit returns Future objects; each Future stores a result or exception. as_completed yields futures, not plain values, so call future.result() inside the loop. Map each future back to its input if the output needs a label. A Future holds the eventual result or exception of a task. as_completed accepts an iterable of Future objects and yields each after it completes. In this browser example both Futures are already finished, so no operating-system thread is started.

  2. 00:41

    Sort the numeric results because the order among already completed Futures is not the contract we want to test. A Future is a handle for work that has been submitted, not the result itself. Storing the handle lets us ask whether the work is done, cancel it if the state permits, or call result when it completes. The as_completed iterator does not change the worker jobs; it changes the order in which the caller learns about them. We can therefore display or store a fast result while a slower sibling is still running. A common mistake is iterating over the original future list and calling result() in that order. If the first job is slow, the loop waits there even when later jobs have finished.

  3. 01:25

    The video uses controlled local ThreadPoolExecutor jobs to make the difference visible. Actual completion order depends on timing and must not be treated as deterministic application data. For live local work, submit functions to a ThreadPoolExecutor, store the returned Futures, then loop over as_completed. This lets the caller react to a fast job while a slower job is still running. Do not claim that every use of threads makes Python computation faster; the use case here is overlapping waits and handling arrival of results. A Future alone does not explain which input was submitted. Keep a mapping from Future to label when calling submit.

  4. 02:08

    In the browser example the mapping is constructed manually, then the result dictionary is compared with the expected labels and values. Dictionary equality ignores insertion order, which matters because completion order can vary. future.result() reraises the exception from that job. Catch a specific exception around each result so one bad item can be reported without hiding successful work. If a job fails, choose whether to continue, cancel others, or fail the whole batch; the appropriate policy depends on the program. as_completed's timeout applies to the iterator and raises TimeoutError when a next completion does not arrive in time.

  5. 02:51

    If the application needs to display results as they arrive, it can print or send each pair immediately. If it needs input order later, store values by label or original index and reorder for presentation. as_completed is about when the caller can handle each completion, not about promising a particular order in the final report. The browser exercise operates on already completed Future objects so it can test result collection without starting operating-system threads. The recorded local example uses ThreadPoolExecutor for real overlapping work. Neither one promises a speedup for CPU- bound Python code under the usual GIL.

  6. 03:34

    Compare this API with the site's asyncio lessons: they solve related coordination problems but have different scheduling and cancellation models. A worker exception does not necessarily interrupt submit. It is retained by the Future and reraised when result() is called. The third example sets a ValueError explicitly so that the error path is reproducible without a thread. Catch the known exception around one result, then decide whether to record a marker or stop the batch. The checked task records '<error>' for ValueError and continues collecting other jobs. That is an exercise policy, not a universal recommendation: some programs

  7. 04:15

    must fail the whole operation instead. Also consider timeouts and cancellation in a real executor. A timeout on as_completed raises when the next completion cannot be obtained within its bound. Rewrite collect_finished(jobs) to loop over as_completed(jobs), call future.result(), and map each completed Future to its label. Return a dictionary of successful results. If a Future raises ValueError, record '<error>' for that label and continue. The browser exercise uses completed futures; run the threaded demonstration locally. The local run deliberately uses different sleep durations so the order is visible,

  8. 04:58

    but production code should never depend on which thread wins a timing race. Tests for the collected values should compare by label or use a set when order is irrelevant. If presentation requires original input order, save each result under its input index and sort afterward. Completion order and display order are separate decisions. At PythonLessonLab, work through the matching lesson and run the checked task. Explain the output before changing the code, then make one small change and rerun the boundary case. The result matters more than memorizing a function name: you should know what it accepts, what it returns, and which failure it prevents.

Understand the concept

Use concurrent.futures.as_completed(futures) when you want to react as submitted tasks finish rather than wait for their original input order. submit returns Future objects; each Future stores a result or exception. as_completed yields futures, not plain values, so call future.result() inside the loop. Map each future back to its input if the output needs a label.

A common mistake is iterating over the original future list and calling result() in that order. If the first job is slow, the loop waits there even when later jobs have finished. The video uses controlled local ThreadPoolExecutor jobs to make the difference visible. Actual completion order depends on timing and must not be treated as deterministic application data.

future.result() reraises the exception from that job. Catch a specific exception around each result so one bad item can be reported without hiding successful work. If a job fails, choose whether to continue, cancel others, or fail the whole batch; the appropriate policy depends on the program. as_completed's timeout applies to the iterator and raises TimeoutError when a next completion does not arrive in time.

The browser exercise operates on already completed Future objects so it can test result collection without starting operating-system threads. The recorded local example uses ThreadPoolExecutor for real overlapping work. Neither one promises a speedup for CPU-bound Python code under the usual GIL. Compare this API with the site's asyncio lessons: they solve related coordination problems but have different scheduling and cancellation models.

  • as_completed yields Future objects as they finish
  • future.result returns a value or reraises an exception
  • Keep a future-to-input mapping
  • Completion order is timing-dependent

See it step by step

Read the code, predict the output, then compare it with the result.

01. Read completed future values

PYTHON
from concurrent.futures import Future,as_completed
a,b=Future(),Future(); a.set_result(2); b.set_result(3)
print(sorted(f.result() for f in as_completed([a,b])))
EXPECTED OUTPUT
[2, 3]

Already finished futures can be consumed without starting threads.

02. Keep input labels

PYTHON
from concurrent.futures import Future,as_completed
a,b=Future(),Future(); a.set_result(2); b.set_result(3)
jobs={a:'first',b:'second'}
print({jobs[f]:f.result() for f in as_completed(jobs)}=={'first':2,'second':3})
EXPECTED OUTPUT
True

The dictionary maps each Future back to the input that created it.

03. Handle a failed future

PYTHON
from concurrent.futures import Future
f=Future(); f.set_exception(ValueError('invalid item'))
try: f.result()
except ValueError as exc: print(type(exc).__name__)
EXPECTED OUTPUT
ValueError

Errors surface when a result is requested.

A closer look

Follow the reasoning, inspect each result, then try the suggested changes in the console below.

01 / 03

Consume finished Future values

A Future holds the eventual result or exception of a task. as_completed accepts an iterable of Future objects and yields each after it completes. In this browser example both Futures are already finished, so no operating-system thread is started. Sort the numeric results because the order among already completed Futures is not the contract we want to test.

For live local work, submit functions to a ThreadPoolExecutor, store the returned Futures, then loop over as_completed. This lets the caller react to a fast job while a slower job is still running. Do not claim that every use of threads makes Python computation faster; the use case here is overlapping waits and handling arrival of results.

PYTHON
from concurrent.futures import Future,as_completed
a,b=Future(),Future(); a.set_result(2); b.set_result(3)
print(sorted(f.result() for f in as_completed([a,b])))
EXPECTED OUTPUT
[2, 3]
Follow the reasoning
  1. Create two completed Futures.
  2. Iterate over as_completed.
  3. Call result() on each yielded Future.
02 / 03

Associate each completion with its input

A Future alone does not explain which input was submitted. Keep a mapping from Future to label when calling submit. In the browser example the mapping is constructed manually, then the result dictionary is compared with the expected labels and values. Dictionary equality ignores insertion order, which matters because completion order can vary.

If the application needs to display results as they arrive, it can print or send each pair immediately. If it needs input order later, store values by label or original index and reorder for presentation. as_completed is about when the caller can handle each completion, not about promising a particular order in the final report.

PYTHON
from concurrent.futures import Future,as_completed
a,b=Future(),Future(); a.set_result(2); b.set_result(3)
jobs={a:'first',b:'second'}
print({jobs[f]:f.result() for f in as_completed(jobs)}=={'first':2,'second':3})
EXPECTED OUTPUT
True
Follow the reasoning
  1. Map Future objects to labels.
  2. Read each completed result.
  3. Compare labeled values without assuming order.
03 / 03

Handle an exception at result()

A worker exception does not necessarily interrupt submit. It is retained by the Future and reraised when result() is called. The third example sets a ValueError explicitly so that the error path is reproducible without a thread. Catch the known exception around one result, then decide whether to record a marker or stop the batch.

The checked task records '<error>' for ValueError and continues collecting other jobs. That is an exercise policy, not a universal recommendation: some programs must fail the whole operation instead. Also consider timeouts and cancellation in a real executor. A timeout on as_completed raises when the next completion cannot be obtained within its bound.

PYTHON
from concurrent.futures import Future
f=Future(); f.set_exception(ValueError('invalid item'))
try: f.result()
except ValueError as exc: print(type(exc).__name__)
EXPECTED OUTPUT
ValueError
Follow the reasoning
  1. Store a failure on a Future.
  2. Call result and observe the reraised ValueError.
  3. Record the exercise marker and continue with other futures.

Try it in Python

Edit the example and run it. Python starts in your browser the first time you click Run.

Python console

Ready to run
Need input()? Add one value per line
Ctrl / ⌘ + Enter to run
OUTPUT
Your output appears here.
Your turnRewrite collect_finished(jobs) to loop over as_completed(jobs), call future.result(), and map each completed Future to its label. Return a dictionary of successful results. If a Future raises ValueError, record '<error>' for that label and continue. The browser exercise uses completed futures; run the threaded demonstration locally.
Need a hint?

Import as_completed. For future in as_completed(jobs): use name=jobs[future], then try future.result() and catch ValueError.

Complete the task and select Check task to verify your code.

    Quick quiz

    Three questions. You can change your answers and try again.

    01.What does as_completed yield?

    02.Can the completion order be relied on across runs?

    03.Where does a worker exception reappear?

    Typical mistakes

    Everyone meets these errors. See what causes them and how to fix them.

    Blocking in submission order

    COMMON MISTAKE
    for future in futures: print(future.result())
    FIX
    for future in as_completed(futures): print(future.result())

    What happens: A slow first job delays handling completed later jobs.
    Process each job when it becomes available.

    Forgetting the result exception

    COMMON MISTAKE
    value = future.result()
    FIX
    try: value = future.result()
    except ValueError: value = '<error>'

    What happens: One failed job aborts the whole collection unexpectedly.
    Choose and document an error policy per job.

    Browse this lesson’s problem fixes

    Sources and further reading