[{"data":1,"prerenderedAt":70},["ShallowReactive",2],{"qa-\u002Fpython\u002Fconcurrency\u002Fconcurrent-futures":3},{"page":4,"siblings":57,"blog":48},{"id":5,"title":6,"body":7,"description":11,"difficulty":14,"extension":15,"framework":16,"frameworkSlug":17,"meta":18,"navigation":19,"order":20,"path":21,"questions":22,"related":48,"seo":49,"seoDescription":50,"stem":51,"subtopic":52,"topic":53,"topicSlug":54,"updated":55,"__hash__":56},"qa\u002Fpython\u002Fconcurrency\u002Fconcurrent-futures.md","Concurrent Futures",{"type":8,"value":9,"toc":10},"minimark",[],{"title":11,"searchDepth":12,"depth":12,"links":13},"",2,[],"medium","md","Python","python",{},true,4,"\u002Fpython\u002Fconcurrency\u002Fconcurrent-futures",[23,27,31,35,40,44],{"id":24,"difficulty":14,"q":25,"a":26},"executor-abstraction","What is the Executor abstraction in concurrent.futures?","`concurrent.futures` provides a **high-level**, uniform interface for running\ncallables asynchronously. An **`Executor`** manages a **pool of workers** and\nhands you back **`Future`** objects representing pending results — and the **same\nAPI** works whether the workers are threads or processes, so you can swap one for\nthe other with a one-line change.\n\n```python\nfrom concurrent.futures import ThreadPoolExecutor\n\ndef work(x):\n    return x * 2\n\nwith ThreadPoolExecutor(max_workers=4) as ex:   # context manager auto-shuts down\n    future = ex.submit(work, 10)                # schedule the call\n    print(future.result())                      # 20\n\n# swap to processes by changing only the class name:\n# with ProcessPoolExecutor() as ex: ...\n```\n\nThe context manager (`with`) cleanly handles worker shutdown and waits for\npending work on exit. Rule of thumb: prefer `concurrent.futures` over raw\n`threading`\u002F`multiprocessing` when you just want to run a function over a pool\nand collect results.\n",{"id":28,"difficulty":14,"q":29,"a":30},"thread-vs-process-pool","When do you use ThreadPoolExecutor vs ProcessPoolExecutor?","Both share the Executor API but differ in workers. **`ThreadPoolExecutor`** runs\ntasks in **threads** within one process — cheap, shared memory, but bound by the\n**GIL**, so it only helps **I\u002FO-bound** work. **`ProcessPoolExecutor`** runs tasks\nin **separate processes**, each with its own GIL, giving **true parallelism** for\n**CPU-bound** work (at the cost of pickling arguments\u002Fresults).\n\n```python\nfrom concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor\n\n# I\u002FO-bound: many network\u002Fdisk waits -> threads\nwith ThreadPoolExecutor() as ex:\n    ex.map(download, urls)\n\n# CPU-bound: number crunching -> processes\nwith ProcessPoolExecutor() as ex:\n    ex.map(crunch, datasets)\n```\n\nBecause the API is identical, you can prototype with threads and switch to\nprocesses if the GIL becomes the bottleneck. Rule of thumb: I\u002FO-bound ->\n`ThreadPoolExecutor`; CPU-bound -> `ProcessPoolExecutor`.\n",{"id":32,"difficulty":14,"q":33,"a":34},"submit-and-futures","What does submit() return, and what is a Future?","**`submit(fn, *args)`** schedules `fn` to run in the pool and **immediately**\nreturns a **`Future`** — a handle to a result that may not exist yet. The Future\nlets you check status (`done()`, `running()`), **block for the result**\n(`result()`), retrieve an exception (`exception()`), `cancel()`, or attach a\ncallback (`add_done_callback`).\n\n```python\nfrom concurrent.futures import ThreadPoolExecutor\n\ndef slow_double(x):\n    return x * 2\n\nwith ThreadPoolExecutor() as ex:\n    fut = ex.submit(slow_double, 21)   # returns instantly\n    print(fut.done())                  # False — probably still running\n    print(fut.result())                # 42 — blocks until ready\n```\n\n`result(timeout=...)` blocks (up to an optional timeout) until the value is\nready. A Future decouples **starting** the work from **collecting** it, which is\nwhat makes overlapping multiple calls possible.\n",{"id":36,"difficulty":37,"q":38,"a":39},"executor-map","easy","How does executor.map work and how does it differ from submit?","**`executor.map(fn, iterable)`** is the convenient bulk form: it applies `fn` to\nevery item concurrently and returns an **iterator of results in input order** —\nanalogous to the built-in `map`, but parallel. **`submit`** is lower level,\ngiving you a Future per call for fine-grained control.\n\n```python\nfrom concurrent.futures import ThreadPoolExecutor\n\ndef fetch(url):\n    return len(url)\n\nurls = [\"a\", \"bb\", \"ccc\"]\nwith ThreadPoolExecutor() as ex:\n    for result in ex.map(fetch, urls):   # results stream back in order\n        print(result)                    # 1, 2, 3\n```\n\n`map` is great when you have a clean iterable and want **ordered** results with\nminimal code. Use `submit` (often with `as_completed`) when you need results\n**as they finish**, per-task error handling, or cancellation. Note `map` raises\nthe first exception when you iterate to that result.\n",{"id":41,"difficulty":14,"q":42,"a":43},"as-completed","What does as_completed do?","**`as_completed(futures)`** yields each Future **as soon as it finishes**,\nregardless of submission order — so you can process results the moment they're\nready instead of waiting for the slowest task to keep its place (as `map`'s\nordered output would).\n\n```python\nfrom concurrent.futures import ThreadPoolExecutor, as_completed\n\ndef work(n):\n    return n * n\n\nwith ThreadPoolExecutor() as ex:\n    futures = [ex.submit(work, i) for i in range(5)]\n    for fut in as_completed(futures):     # whichever finishes first\n        print(fut.result())               # order is non-deterministic\n```\n\nThis is ideal for **responsiveness** — show progress as tasks complete, or\nhandle failures immediately. Use `map` when you want results in input order; use\n`as_completed` when you want them in **completion** order.\n",{"id":45,"difficulty":14,"q":46,"a":47},"exception-propagation","How are exceptions handled with futures?","An exception raised inside a worker is **captured and stored** in its Future,\nnot raised at submit time. It **re-raises when you call `future.result()`** (or\niterate to that item in `map`). You can also inspect it without raising via\n**`future.exception()`**.\n\n```python\nfrom concurrent.futures import ThreadPoolExecutor\n\ndef boom(x):\n    raise ValueError(f\"bad: {x}\")\n\nwith ThreadPoolExecutor() as ex:\n    fut = ex.submit(boom, 1)\n    err = fut.exception()        # returns the ValueError, doesn't raise\n    print(err)                   # bad: 1\n    fut.result()                 # NOW it re-raises ValueError\n```\n\nThe implication: if you never call `result()` (or check `exception()`), an\nerror can **pass silently**. Always retrieve results — typically in a `try\u002Fexcept`\naround `result()` — so worker failures surface in the main thread.\n",null,{"description":11},"Python interview questions on concurrent.futures: the Executor abstraction, ThreadPoolExecutor vs ProcessPoolExecutor, submit and Future objects, map, as_completed, and exception handling.","python\u002Fconcurrency\u002Fconcurrent-futures","concurrent.futures","Concurrency & Parallelism","concurrency","2026-06-18","EBXrpKsBJ9HKBfCU5qZ9GBoP7V5poZ4M3DOQAmPFiLQ",[58,62,65,69],{"subtopic":59,"path":60,"order":61},"Threading & the GIL","\u002Fpython\u002Fconcurrency\u002Fgil",1,{"subtopic":63,"path":64,"order":12},"Multiprocessing","\u002Fpython\u002Fconcurrency\u002Fmultiprocessing",{"subtopic":66,"path":67,"order":68},"asyncio & async\u002Fawait","\u002Fpython\u002Fconcurrency\u002Fasyncio",3,{"subtopic":52,"path":21,"order":20},1781808681489]