In Kotlin coroutines, what happens when a coroutine started with `launch` throws an uncaught exception?
answer
- launch = fire-and-forget, returns Job
- uncaught throw fails the Job
- failure cancels parent then siblings
- try/catch around launch catches nothing
- catch inside the body instead
basics
~10 sThe coroutine fails. The error travels up to its parent, which cancels itself and its other child coroutines, and then the error is reported.
solid answer
~40 sA `launch` coroutine is fire-and-forget: an uncaught exception immediately fails its `Job`. The failure propagates up the parent-child `Job` hierarchy. The parent is cancelled, which in turn cancels all its other children (the failing coroutine's siblings). The exception is not stored or returned anywhere — once it reaches the root of a regular (non-supervisor) scope, it is delivered to the `CoroutineExceptionHandler` if one is installed, otherwise to the thread's default uncaught-exception handler. The exception is thrown at the point of failure inside the coroutine, not at the `launch` call site, so wrapping `launch { ... }` in try/catch does NOT catch it. You catch it either inside the coroutine body or via a `CoroutineExceptionHandler`.
code
kotlin · 11 linesimport kotlinx.coroutines.*
fun main() = runBlocking {
val scope = CoroutineScope(Job())
val sibling = scope.launch {
try { delay(1000) } finally { println("sibling cancelled") }
}
scope.launch { throw IllegalStateException("boom") } // fails
sibling.join()
// sibling is cancelled because its sibling failed the shared parent
}go deeper
Knows launch is fire-and-forget and that an uncaught throw is reported rather than returned.
Explains that try/catch around launch fails and that handling must happen inside the body.
Connects the behavior to the Job tree: parent cancellation then sibling cancellation, plus the reporting step.
Frames it as structured concurrency by design and contrasts with supervisor scopes where this propagation is intentionally broken.
## What `launch` is `launch` is a coroutine builder that starts a *fire-and-forget* coroutine and returns a `Job` (a handle to its lifecycle). Unlike `async`, it has no result to return, so it has nowhere to *store* a thrown exception — it treats any uncaught exception as a **failure** of its `Job`. ## Structured concurrency and the Job tree Every coroutine has a `Job`. When you `launch` inside a `CoroutineScope`, the new `Job` becomes a **child** of the scope's `Job`. This forms a tree. The rule for a regular `Job` (not a `SupervisorJob`): **a child's failure fails the parent**. So when a `launch` body throws: 1. The coroutine's `Job` moves to a failed state. 2. It **cancels its parent** with that exception. 3. The parent **cancels all its other children** — the siblings — by throwing `CancellationException` into them. 4. The original exception keeps propagating up until it reaches the scope root, where it is **reported** (to a `CoroutineExceptionHandler` or the default handler). ## Why try/catch around `launch` does not work ```kotlin // WRONG — this catches nothing try { scope.launch { throw IllegalStateException("boom") } } catch (e: Exception) { // never reached: launch returns immediately, // the throw happens later, on another dispatch } ``` `launch { ... }` returns a `Job` *immediately*; the body runs later (possibly on another thread). The exception is thrown deep inside the coroutine machinery, not synchronously at the call site. To handle it you must catch **inside** the body: ```kotlin scope.launch { try { riskyWork() } catch (e: IllegalStateException) { // handled here — no propagation } } ``` ## Reporting vs handling A propagated exception is *reported* at the root: if a `CoroutineExceptionHandler` is in the scope's context it receives the exception; otherwise the JVM's default uncaught-exception handler prints it. (The handler mechanics are a separate topic — here the key point is *that* propagation reaches a reporting point.) ## One subtlety: `CancellationException` If the body throws `CancellationException` (or `kotlinx.coroutines.CancellationException`), that is treated as **normal cancellation**, not a failure — it does not cancel the parent or siblings. Every other exception type is a failure that propagates as above.
- Why does wrapping `launch { ... }` in try/catch not catch the exception?`launch` returns a `Job` synchronously and runs the body later (often on another thread). The throw happens inside the coroutine, not at the call site, so the surrounding try/catch never sees it.
- How do you actually handle the exception from a launched coroutine?Catch it inside the coroutine body, or install a `CoroutineExceptionHandler` in the scope's context to receive propagated, otherwise-uncaught exceptions.
A child throwing a tantrum (failure) ruins the whole family outing — the parent calls it off and pulls the other kids home too.
saying these in an interview costs you the question
- Claiming try/catch around the launch call catches the body's exception
- Thinking launch returns the result or stores the exception like async
- Believing the exception only kills the one coroutine and leaves siblings running
- Confusing launch with async/await semantics