Futures made by hand
No threads needed: a Future is just a box with a state. An executor is a machine that fills these boxes.
concurrent.futures gives you a pool of workers and a Future for every job you submit. ThreadPoolExecutor suits blocking I/O, ProcessPoolExecutor suits CPU-bound work, and both share the same simple API: submit, map and as_completed.
Change the code and press Run (Ctrl+Enter). Predict what will happen first, then break it on purpose and read what changes. Your edits are saved and match the lesson page.
Ready for more? This phase has 6 hands-on exercises with automatic checks.
Practice questions
Write the code in the editor, run it, then open the model answer to compare.
Create a Future, attach a callback that prints got 7, then set its result to 7.
Write first_error(futures) that returns the first exception stored in a list of finished futures, or None.
Write all_results(futures) that returns a list of results, with "failed" in place of any future that holds an exception.
Explain it without notes
What is a Future, and how does result() treat exceptions?
How do map and as_completed differ in the order of results?
When would you choose ThreadPoolExecutor over ProcessPoolExecutor, and the other way round?
How do you decide the size of a pool?
Inside a with ThreadPoolExecutor() block your code raises an exception after submitting 100 jobs. What happens to the jobs, and how would you stop them sooner?
Expected output
new future -> done: False | cancelled: False
callback sees: 42
after set_result -> done: True | result: 42
exception stored: ValueError('model said no')
result() raised: model said no
cancel a pending future: True | cancelled: True
result() raised CancelledError
cancel a finished future: False