How does supervisorScope { } differ from coroutineScope { } with respect to child failures?
answer
- Both wait for all children, both cancel downward
- coroutineScope: any failure cancels all + rethrows from builder
- supervisorScope: failure isolated, NOT rethrown by builder
- try/catch around supervisorScope won't catch child errors
- async failure surfaces on await() in both
basics
~10 sBoth wait for all child coroutines to finish. In coroutineScope, one child failing cancels all the others and throws. In supervisorScope, a failing child does not cancel its siblings.
solid answer
~40 sBoth `coroutineScope { }` and `supervisorScope { }` are suspending builders that create a child scope, run the block, and suspend until every child coroutine completes. The difference is the backing Job. `coroutineScope` uses a regular Job: if any child fails, the scope cancels all remaining children and rethrows the exception from the builder call. `supervisorScope` uses a SupervisorJob: a child's failure does not cancel siblings and is NOT rethrown by the builder — instead it propagates to that child's own CoroutineExceptionHandler (for launch) or surfaces on await() (for async). Both still propagate **downward** cancellation, and both wait for children before returning. Use `coroutineScope` for all-or-nothing work (decompose one task into parallel parts); use `supervisorScope` when children are independent and one failure should not abort the rest.
go deeper
Knows that coroutineScope cancels all children on failure while supervisorScope isolates them.
Explains that supervisorScope does NOT rethrow from the builder and that exceptions must be handled per-child.
Distinguishes propagation paths (CoroutineExceptionHandler vs await), and that both wait for children and cancel downward.
Advises on choosing all-or-nothing vs independent semantics per use case and the testability/error-reporting implications.
## The two builders Both are **suspending functions** that establish structured concurrency for a block of code: - They create a new child scope whose Job is a child of the current coroutine's Job. - They run the lambda (which can `launch`/`async` children). - They **suspend the caller until all children complete**, then return. The key difference is the **type of Job** they install. ## coroutineScope { } — regular Job, all-or-nothing Backed by a normal `Job`. If **any** child fails: 1. All other children are cancelled. 2. The exception is **rethrown from the `coroutineScope(...)` call** itself, so you can wrap it in `try/catch`. ```kotlin try { coroutineScope { launch { delay(100); println("never prints") } launch { throw RuntimeException("boom") } } } catch (e: RuntimeException) { println("caught: ${e.message}") // caught: boom } ``` ## supervisorScope { } — SupervisorJob, independent children Backed by a `SupervisorJob`. A child's failure: - Does **not** cancel siblings. - Is **not** rethrown by the builder — wrapping `supervisorScope { }` in try/catch will not catch a child's exception. - For `launch` children, it goes to the nearest `CoroutineExceptionHandler` in that child's context (or the uncaught handler). - For `async` children, it is stored and rethrown only when you call `await()`. ```kotlin supervisorScope { launch(CoroutineExceptionHandler { _, e -> println("handled: ${e.message}") }) { throw RuntimeException("boom") } launch { delay(200); println("sibling survives") } } ``` ## Shared behavior - Both **wait for all children** (the scope does not return early). - Both propagate **downward cancellation**: cancelling the outer coroutine cancels the whole scope. - A direct exception thrown by the block body itself (not a child) cancels the scope in both cases. ## Choosing between them - **coroutineScope**: parallel decomposition of one logical task — if any part fails the whole thing is meaningless. - **supervisorScope**: a batch of independent tasks (e.g. fan-out requests) where partial failure is acceptable.
- Can you catch a child's exception by wrapping supervisorScope in try/catch?No. supervisorScope does not rethrow child failures from the builder. Catch it inside the child (try/catch in the launch block), via a CoroutineExceptionHandler on the child, or on await() for async.
- If the supervisorScope block body itself throws (outside any launch), what happens?That cancels the scope and propagates like a normal exception from the builder — supervisor behavior only isolates child coroutines, not the body.
saying these in an interview costs you the question
- Saying supervisorScope rethrows child exceptions from the builder
- Believing supervisorScope returns before children finish
- Claiming coroutineScope keeps siblings alive on failure
- Forgetting both still cancel downward
- Suggesting try/catch around supervisorScope catches child errors