Explain the failure-propagation difference between coroutineScope and supervisorScope when a child coroutine throws.
answer
- coroutineScope = Job, fail cancels siblings
- supervisorScope = SupervisorJob, isolated failures
- SupervisorJob propagates downward only
- launch failure -> handler; async failure -> await()
- interdependent vs independent tasks
basics
~10 sIn coroutineScope, if one child fails, the others get cancelled and the failure spreads up. In supervisorScope, one child failing does not cancel its siblings; each child fails on its own.
solid answer
~40 sBoth builders wait for all children, but they install different Jobs. coroutineScope uses a regular Job: a child failure cancels the parent and therefore all sibling children, and the exception is rethrown from coroutineScope. supervisorScope installs a SupervisorJob: failures flow downward only, so a failing child does NOT cancel siblings or the scope. With supervisorScope, an uncaught failure in a launch child goes to the CoroutineExceptionHandler (or crashes if none), while siblings keep running; for async children the exception surfaces when you call await(). The choice is about whether sibling tasks are interdependent (use coroutineScope) or independent (use supervisorScope, e.g. a server handling independent requests).
code
kotlin · 7 linessuspend fun demo() = supervisorScope {
val ok = async { 42 }
val bad = async<Int> { error("nope") }
println(ok.await()) // 42 — sibling unaffected
try { bad.await() } // exception surfaces here
catch (e: IllegalStateException) { println("caught: ${e.message}") }
}go deeper
States the headline rule: coroutineScope cancels siblings on failure, supervisorScope does not.
Explains it via Job vs SupervisorJob and that SupervisorJob propagates only downward.
Adds correct exception delivery: launch -> CoroutineExceptionHandler, async -> await(); and the try/catch-around-launch gotcha.
Reasons about API design: when to expose fail-fast vs isolated semantics, and pairing supervisorScope with per-child handlers in a long-lived service.
## The two builders share a shape Both `coroutineScope` and `supervisorScope` are **suspend functions** that create a scope, run a block, and **wait for all children**. The difference is the **Job** they install and therefore how **cancellation and exceptions propagate**. ## coroutineScope — failures cancel siblings `coroutineScope` uses a normal `Job`. In structured concurrency, a child failing **cancels its parent**, and a cancelled parent **cancels all its other children**. So: - One child throws → siblings are cancelled → `coroutineScope` rethrows the original exception. - This is *all-or-nothing*: use it when the tasks form one logical unit (e.g. you need both results to build a response). ```kotlin suspend fun bothOrNothing() = coroutineScope { launch { delay(50); error("boom") } // fails launch { delay(1000); println("never prints") } // cancelled } // throws IllegalStateException("boom") ``` ## supervisorScope — failures stay isolated `supervisorScope` installs a **`SupervisorJob`**. A `SupervisorJob` only propagates cancellation **downward** (parent → children), never **upward** (child failure does NOT cancel the parent or siblings). - A failing `launch` child does **not** cancel siblings. Its exception is delivered to the `CoroutineExceptionHandler` in context, or — if none — it propagates as an uncaught exception when the scope completes. - A failing `async` child does not cancel siblings either; the exception is **stored** and rethrown when you call `await()`. ```kotlin suspend fun independent() = supervisorScope { launch { error("task A failed") } // does not stop B launch { delay(100); println("B done") } // still runs } ``` ## Catching exceptions correctly A subtle point: putting `try/catch` *around* `launch` does **not** catch a failure inside the launched child — `launch` returns immediately. You must handle exceptions **inside** the child, or use a `CoroutineExceptionHandler` for top-level `launch`. For `async`, wrap the `await()` call in `try/catch`. ## Choosing - **Interdependent results, fail fast** → `coroutineScope`. - **Independent tasks, one failure must not kill the rest** (request handlers, fan-out of best-effort jobs) → `supervisorScope`.
- In supervisorScope, where does an uncaught exception from a launch child go?To the CoroutineExceptionHandler installed in the coroutine context; if none is present it is treated as uncaught and surfaces when the scope finishes (can crash the app).
- Why doesn't wrapping launch in try/catch catch the child's exception?launch returns a Job immediately and runs the body asynchronously, so the throw happens outside the try block. Handle it inside the child or via a CoroutineExceptionHandler.
coroutineScope is a team where one person quitting cancels the whole project; supervisorScope is a manager whose team members can fail without taking down the others.
saying these in an interview costs you the question
- Saying supervisorScope makes exceptions disappear
- Believing try/catch around launch catches the child failure
- Confusing the direction of SupervisorJob propagation
- Claiming coroutineScope lets siblings keep running after a failure
- Not knowing async stores the exception until await()