concurrent.futures
Playground note:
concurrent.futuresspawns OS threads/processes — browser sandboxes cannot do this. Run the code snippets locally withpython3. The<TsGo>,<Quiz>, and<ProgressTracker>components still work normally.
The high-level API
Section titled “The high-level API”concurrent.futures is Python’s answer to “I want threads or processes but I don’t want to manage them manually.” It provides a clean, unified API that feels much closer to JavaScript’s Promise-based model than raw threading or multiprocessing.
The two executors are drop-in replacements for each other — you switch between thread-based and process-based concurrency by changing one line.
// TypeScript — Promise.all over an array (built-in)const tasks = [1, 2, 3, 4, 5].map((n) => fetch(`/api/item/${n}`).then((r) => r.json()));const results = await Promise.all(tasks);
// No need for explicit thread pools — JS handles it# Python — concurrent.futures is the closest equivalentfrom concurrent.futures import ThreadPoolExecutor
def fetch_item(n: int) -> dict: import time; time.sleep(0.1) # simulate I/O return {"id": n, "value": n * 2}
with ThreadPoolExecutor(max_workers=5) as executor: # executor.map — like Promise.all over an array results = list(executor.map(fetch_item, [1, 2, 3, 4, 5]))
print(results)ThreadPoolExecutor — I/O-bound work
Section titled “ThreadPoolExecutor — I/O-bound work”from concurrent.futures import ThreadPoolExecutor, as_completedimport time
def download(url: str) -> str: time.sleep(0.1) # simulate network I/O return f"Downloaded: {url}"
urls = [ "https://api.example.com/users", "https://api.example.com/posts", "https://api.example.com/tags",]
# executor.map — results in INPUT order (like Promise.all)with ThreadPoolExecutor(max_workers=3) as executor: for result in executor.map(download, urls): print(result)
print("---")
# executor.submit + as_completed — results in COMPLETION orderwith ThreadPoolExecutor(max_workers=3) as executor: futures = {executor.submit(download, url): url for url in urls} for future in as_completed(futures): url = futures[future] print(f"{url} → {future.result()}")Run this locally — browser sandboxes can’t spawn OS threads/processes.
ProcessPoolExecutor — CPU-bound work
Section titled “ProcessPoolExecutor — CPU-bound work”from concurrent.futures import ProcessPoolExecutor
def is_prime(n: int) -> bool: if n < 2: return False for i in range(2, int(n ** 0.5) + 1): if n % i == 0: return False return True
if __name__ == "__main__": numbers = list(range(1_000_000, 1_000_100))
# ThreadPoolExecutor: limited by GIL — no speedup for CPU work # ProcessPoolExecutor: each process has its own GIL → real parallelism with ProcessPoolExecutor(max_workers=4) as executor: primes = [n for n, p in zip(numbers, executor.map(is_prime, numbers)) if p] print(primes[:5])Future objects — inspect individual results
Section titled “Future objects — inspect individual results”from concurrent.futures import ThreadPoolExecutor, Futureimport time
def task(n: int) -> str: time.sleep(n * 0.01) return f"result-{n}"
with ThreadPoolExecutor(max_workers=3) as executor: f1: Future[str] = executor.submit(task, 1) f2: Future[str] = executor.submit(task, 2) f3: Future[str] = executor.submit(task, 3)
# .result() blocks until done — like await promise print(f1.result()) # "result-1" print(f2.result()) # "result-2"
# Check state without blocking print(f3.done()) # True/False print(f3.running()) # True/FalseChoosing between ThreadPoolExecutor and ProcessPoolExecutor
Section titled “Choosing between ThreadPoolExecutor and ProcessPoolExecutor”// TypeScript// No explicit choice needed — the event loop handles I/O,// Web Workers handle CPU. The runtime decides threading.# Python — you choose explicitlyfrom concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor
# I/O-bound (network, disk, DB calls with blocking libraries)with ThreadPoolExecutor(max_workers=20) as executor: results = list(executor.map(blocking_http_call, urls))
# CPU-bound (math, compression, parsing, ML inference)with ProcessPoolExecutor(max_workers=4) as executor: results = list(executor.map(cpu_intensive_fn, data))
# Rule of thumb for max_workers:# Threads: 5 * os.cpu_count() (I/O-bound — threads mostly sleep)# Processes: os.cpu_count() (CPU-bound — one per core is enough)