Python Academy · Lesson

ThreadPoolExecutor

Run tasks concurrently.

Lesson 2 of 413 steps

ThreadPoolExecutor is a free Python Academy lesson on CoddyKit. This is lesson 2 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the Python Academy learning path, and your progress syncs across the web and the CoddyKit app. The Python Academy course includes 4 lessons in total.

Why a Thread Pool?

Creating and joining threads by hand is tedious. concurrent.futures.ThreadPoolExecutor manages a pool of worker threads for you and gives back results cleanly.

from concurrent.futures import ThreadPoolExecutor

def square(n):
    return n * n

with ThreadPoolExecutor() as ex:
    future = ex.submit(square, 5)
    print(future.result())

submit and Future

submit() schedules a call and immediately returns a Future. You call .result() on it to get the value, blocking until it is ready.

from concurrent.futures import ThreadPoolExecutor

def greet(name):
    return 'hi ' + name

with ThreadPoolExecutor(max_workers=2) as ex:
    f1 = ex.submit(greet, 'Ana')
    f2 = ex.submit(greet, 'Bob')
    print(f1.result())
    print(f2.result())

map for Many Inputs

executor.map(func, iterable) runs the function over every item concurrently and yields results in input order.

from concurrent.futures import ThreadPoolExecutor

def cube(n):
    return n ** 3

with ThreadPoolExecutor() as ex:
    results = ex.map(cube, range(5))
    print(list(results))

Controlling Worker Count

max_workers caps how many threads run at once. For I/O-bound work you can use more workers than CPU cores.

from concurrent.futures import ThreadPoolExecutor
import time

def io_task(n):
    time.sleep(0.05)
    return n

start = time.perf_counter()
with ThreadPoolExecutor(max_workers=4) as ex:
    print(list(ex.map(io_task, range(4))))
print('ran concurrently')

as_completed

as_completed(futures) yields each future the moment it finishes, regardless of submission order. Great for processing results as soon as they arrive.

from concurrent.futures import ThreadPoolExecutor, as_completed
import time

def work(n):
    time.sleep(0.01 * (3 - n))
    return n

with ThreadPoolExecutor() as ex:
    futures = [ex.submit(work, i) for i in range(3)]
    for f in as_completed(futures):
        print('finished', f.result())

Handling Exceptions

If a task raises, the exception is stored in its Future and re-raised when you call .result(). Wrap that call in try/except.

from concurrent.futures import ThreadPoolExecutor

def risky(n):
    if n == 0:
        raise ValueError('cannot be zero')
    return 10 // n

with ThreadPoolExecutor() as ex:
    f = ex.submit(risky, 0)
    try:
        print(f.result())
    except ValueError as e:
        print('caught:', e)

Mapping With Multiple Args

map accepts several iterables and zips them as arguments, like the built-in map.

from concurrent.futures import ThreadPoolExecutor

def add(a, b):
    return a + b

with ThreadPoolExecutor() as ex:
    print(list(ex.map(add, [1, 2, 3], [10, 20, 30])))

Collecting Results in a Dict

A common pattern maps each future back to its input so you know which result is which when using as_completed.

from concurrent.futures import ThreadPoolExecutor, as_completed

def length(word):
    return len(word)

words = ['cat', 'tiger', 'ox']
with ThreadPoolExecutor() as ex:
    future_to_word = {ex.submit(length, w): w for w in words}
    out = {}
    for f in as_completed(future_to_word):
        out[future_to_word[f]] = f.result()
print(sorted(out.items()))

Context Manager Shutdown

Using the executor in a with block calls shutdown() automatically, waiting for all pending tasks to finish.

from concurrent.futures import ThreadPoolExecutor

def job(n):
    return n * 2

with ThreadPoolExecutor() as ex:
    futures = [ex.submit(job, i) for i in range(3)]
print('all tasks done after the with block')
print([f.result() for f in futures])

Checking Future State

A Future exposes .done() and .running() so you can inspect progress without blocking.

from concurrent.futures import ThreadPoolExecutor

def quick(n):
    return n + 1

with ThreadPoolExecutor() as ex:
    f = ex.submit(quick, 41)
    result = f.result()
    print('done?', f.done())
    print('value:', result)

When to Use It

ThreadPoolExecutor is ideal for many small I/O-bound tasks: HTTP calls, file reads, database queries. For CPU-bound work, use ProcessPoolExecutor instead.

from concurrent.futures import ThreadPoolExecutor

urls = ['a', 'b', 'c']

def fetch(u):
    return 'fetched ' + u

with ThreadPoolExecutor(max_workers=3) as ex:
    for r in ex.map(fetch, urls):
        print(r)

Quick Check

Test your understanding of ThreadPoolExecutor.

Recap

You learned ThreadPoolExecutor:

  • submit() returns a Future; map() runs over an iterable in order.
  • as_completed() yields futures as they finish.
  • Exceptions surface via result().
  • The with block handles shutdown.

Next: true parallelism with multiprocessing.

Free to start

Learn Python with an AI tutor — free

Write and run real code in your browser, get instant help from a 24/7 AI tutor, and pick up where you left off on the web or in the app.

Courses
76
Lessons
320

Frequently Asked Questions

Is the “ThreadPoolExecutor” lesson free?

Yes — the full text of “ThreadPoolExecutor” is free to read here on the web. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the Python Academy course, upgrade to CoddyKit PRO. The Python Academy course includes 4 lessons in total.

What will I learn in “ThreadPoolExecutor”?

Run tasks concurrently. You practise Python Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.

Do I need any experience to start Python Academy?

No prior experience is required. Python Academy on CoddyKit is structured for beginners through advanced learners, so you can start here or from the beginning and move at your own pace. This is lesson 2 of 4.

How long does the “ThreadPoolExecutor” lesson take?

Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.

Can I write and run code in this Python Academy lesson?

Yes. Every Python Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.

All lessons in this course

  1. Threads and the GIL
  2. ThreadPoolExecutor
  3. multiprocessing Basics
  4. Sharing Data Safely
← Back to Python Academy