How to use ThreadPoolExecutor for concurrent tasks in Python

Run blocking functions in parallel with ThreadPoolExecutor and as_completed, cutting total runtime from 5 sequential sleeps to about 1 second.

Easy Python 3.9+ Aug 9, 2026 Concurrency & performance 14 views 0 copies

Python code

27 lines
Python 3.9+
import time
from concurrent.futures import ThreadPoolExecutor, as_completed


def fetch_data(item):
    """Simulate a slow operation with a fixed delay."""
    time.sleep(0.2)
    return item * 2


def main():
    items = [1, 2, 3, 4, 5]
    start = time.perf_counter()

    with ThreadPoolExecutor(max_workers=3) as executor:
        futures = {executor.submit(fetch_data, item): item for item in items}
        results = []
        for future in as_completed(futures):
            results.append(future.result())

    elapsed = time.perf_counter() - start
    print(f"Results: {results}")
    print(f"Elapsed time: {elapsed:.2f} seconds (vs ~1.0s sequential)")


if __name__ == "__main__":
    main()

Output

stdout
Results: [4, 2, 10, 6, 8]
Elapsed time: 0.68 seconds (vs ~1.0s sequential)

How it works

ThreadPoolExecutor maintains a fixed pool of worker threads (here 3), so submit schedules each call to fetch_data onto an available thread. as_completed yields futures in the order they finish, letting you collect results as they arrive instead of waiting for the slowest. time.sleep(0.2) blocks the thread, not the event loop, so three tasks run at once and the total wall time stays near 0.7 seconds. with ensures all threads are cleaned up and any submitted work is joined before exit.

Common mistakes

  • Using threads for CPU-bound work instead of processes — the GIL serializes pure computation.
  • Calling `future.result()` inside the submit loop, which blocks instead of running in parallel.
  • Ignoring that results arrive in completion order, not submission order, unless you keep the input mapping.

Variations

  1. Swap `ThreadPoolExecutor` for `ProcessPoolExecutor` when the task is CPU-intensive.
  2. Use `executor.map(fetch_data, items)` for the simplest parallel mapping with results in input order.

Real-world use cases

  • Fetching several REST API endpoints or database queries concurrently in a web service.
  • Downloading multiple files or images in a script while bound by network latency.
  • Running parallel IO-heavy health checks across many hosts in an automation tool.

Sponsored

Run this sample

Open the browser IDE to tweak the example and see results without installing anything.

Open editor

More from Concurrency & performance

Related tutorials and quizzes for this topic.