{
    "slug": "structured_concurrency",
    "term": "Structured Concurrency",
    "category": "concurrency",
    "difficulty": "advanced",
    "short": "A model where child tasks live inside a parent scope that waits for all of them to finish before it exits, so no task is ever orphaned.",
    "long": "Structured concurrency binds the lifetime of concurrent tasks to a lexical scope. When you spawn child tasks inside a scope (a nursery, task group, or async with block), the scope does not return until every child has completed, errored, or been cancelled. This mirrors how structured programming replaced goto with blocks: control flow always converges, so you can reason about it locally.\n\nThe key property is that there are no orphaned or 'leaked' tasks. In unstructured models you call something like go(), spawn(), or fire a Promise without awaiting it, and that task floats free of any owner. If it crashes, the error vanishes; if the program shuts down, the task may be killed mid-write; if you forget it exists, it leaks resources. Structured concurrency makes that impossible by construction - the parent owns the children.\n\nError propagation is the second benefit. If one child fails, the scope cancels its siblings and propagates the exception to the parent, rather than letting one failure silently disappear into a detached task. Cancellation is also scoped: cancelling the parent cancels every descendant, giving deterministic teardown.\n\nConcrete implementations include Python's asyncio.TaskGroup (3.11+) and the Trio nursery that inspired it, Java 21's StructuredTaskScope, and Kotlin's coroutine scopes. PHP does not have a first-class primitive, but Amp v3's Future combinators and ReactPHP supervision approximate the pattern by awaiting a known set of children before continuing.\n\nUse it whenever you fan out work and need all results back, or when a request handler spawns helpers that must not outlive the request. The trade-off is that genuinely long-lived background work (a daemon loop, a connection keep-alive) does not fit a request-scoped lifetime and needs an explicitly longer-lived owning scope instead. Structured concurrency is about making task lifetimes visible and bounded, not about forbidding background work.",
    "aliases": [
        "task groups",
        "nursery pattern",
        "scoped concurrency",
        "structured task scope"
    ],
    "tags": [
        "concurrency",
        "task-lifecycle",
        "cancellation",
        "error-propagation",
        "async"
    ],
    "misconception": "Structured concurrency is just a tidier way to await a list of promises. In fact its defining guarantee is that the scope cannot exit while any child is still running, so a sibling failure cancels the others and no task ever escapes its parent.",
    "why_it_matters": "Orphaned tasks swallow errors, leak connections, and shut down mid-operation; scoping task lifetimes to a parent makes failures propagate and resources clean up deterministically.",
    "common_mistakes": [
        "Spawning fire-and-forget tasks (go/spawn/unawaited Promise) that outlive the scope that created them.",
        "Swallowing a child task's exception because nothing ever awaits its result.",
        "Forgetting to cancel sibling tasks when one fails, so they keep running after the parent has decided to abort.",
        "Putting genuinely long-lived background work inside a short-lived request scope, then wondering why it gets killed.",
        "Assuming a task group gives parallelism rather than concurrency - it still depends on the underlying executor."
    ],
    "when_to_use": [
        "Fanning out work where the parent must collect every result before continuing.",
        "Request handlers that spawn helpers which must not outlive the request.",
        "Anywhere you need one child's failure to cancel siblings and propagate cleanly.",
        "When deterministic teardown and no leaked tasks are correctness requirements."
    ],
    "avoid_when": [
        "You need a long-lived daemon or background loop whose lifetime must outlast any single request scope.",
        "The language or runtime has no cancellation support, making sibling cancellation impossible to honour.",
        "Tasks are genuinely independent and you explicitly want them to survive the spawning function (use a dedicated supervised owner instead)."
    ],
    "related": [
        "coroutines",
        "promises_futures",
        "async_vs_parallel",
        "js_abort_controller",
        "php_concurrency_options"
    ],
    "prerequisites": [
        "coroutines",
        "promises_futures",
        "async_vs_parallel"
    ],
    "refs": [
        "https://docs.python.org/3/library/asyncio-task.html#task-groups",
        "https://docs.oracle.com/en/java/javase/21/core/structured-concurrency.html",
        "https://vorpus.org/blog/notes-on-structured-concurrency-or-go-statement-considered-harmful/"
    ],
    "bad_code": "# Unstructured: detached tasks leak and swallow errors\nasync def handle():\n    asyncio.create_task(fetch_user())   # orphaned - nothing awaits it\n    asyncio.create_task(fetch_orders()) # if this raises, error vanishes\n    return 'done'  # returns while children may still be running",
    "good_code": "# Structured: scope waits for all children, propagates failures\nasync def handle():\n    async with asyncio.TaskGroup() as tg:\n        user_t   = tg.create_task(fetch_user())\n        orders_t = tg.create_task(fetch_orders())\n    # block exits only when both finish; if one raises,\n    # the other is cancelled and the error propagates here\n    return {'user': user_t.result(), 'orders': orders_t.result()}",
    "quick_fix": "Replace detached spawns with a task group / nursery that awaits all children, propagates the first error, and cancels siblings on failure.",
    "severity": "medium",
    "effort": "medium",
    "created": "2026-06-10",
    "updated": "2026-06-10",
    "citation": {
        "canonical_url": "https://codeclaritylab.com/glossary/structured_concurrency",
        "html_url": "https://codeclaritylab.com/glossary/structured_concurrency",
        "json_url": "https://codeclaritylab.com/glossary/structured_concurrency.json",
        "source": "CodeClarityLab Glossary",
        "author": "P.F.",
        "author_url": "https://pfmedia.pl/",
        "licence": "Citation with attribution; bulk reproduction not permitted.",
        "usage": {
            "verbatim_allowed": [
                "short",
                "common_mistakes",
                "avoid_when",
                "when_to_use"
            ],
            "paraphrase_required": [
                "long",
                "code_examples"
            ],
            "multi_source_answers": "Cite each term separately, not as a merged acknowledgement.",
            "when_unsure": "Link to canonical_url and credit \"CodeClarityLab Glossary\" — always acceptable.",
            "attribution_examples": {
                "inline_mention": "According to CodeClarityLab: <quote>",
                "markdown_link": "[Structured Concurrency](https://codeclaritylab.com/glossary/structured_concurrency) (CodeClarityLab)",
                "footer_credit": "Source: CodeClarityLab Glossary — https://codeclaritylab.com/glossary/structured_concurrency"
            }
        }
    }
}