How is `CancellationException` treated differently from other exceptions in coroutine propagation?
answer
- CancellationException = normal cancellation, not failure
- does NOT cancel parent or siblings
- thrown at suspension points (delay/yield/withContext)
- never swallow it — rethrow
- everything else propagates and fails the tree
basics
~10 sCancellationException means a coroutine was cancelled normally. It does not fail the parent or cancel siblings. Any other exception is a real failure and does propagate up.
solid answer
~40 sCoroutines use `CancellationException` as the in-band signal for cooperative cancellation: when you call `cancel()` or a parent cancels a child, the child's suspension points throw `CancellationException`. The machinery treats it as **normal, expected termination of that coroutine only** — it does NOT propagate to fail the parent or cancel siblings. Every *other* `Throwable` is a real failure that propagates up the `Job` tree, cancelling parent and siblings. A dangerous trap: catching `CancellationException` in a broad `catch (e: Exception)` and swallowing it breaks cancellation cooperation, because the coroutine then ignores its own cancellation. Best practice is to rethrow it (or use `coroutineContext.ensureActive()` / `currentCoroutineContext().isActive`). Note `kotlinx.coroutines.CancellationException` is a typealias for `java.util.concurrent.CancellationException`.
code
kotlin · 11 linesimport kotlinx.coroutines.*
suspend fun safe() {
try {
delay(1000)
} catch (e: CancellationException) {
throw e // preserve cancellation
} catch (e: Exception) {
// genuine failure handling
}
}go deeper
Knows CancellationException means 'cancelled', distinct from a crash.
Explains it does not fail the parent/siblings and that swallowing it is a bug; rethrows it.
Discusses NonCancellable for cleanup and ensureActive() for cooperative re-checking.
Articulates why the asymmetry is essential to structured concurrency and how it interacts with custom CancellationException subclasses.
## Two kinds of throwables in a coroutine When something is thrown inside a coroutine, the framework asks one question: **is it a `CancellationException`?** - **`CancellationException`** → "this coroutine is being cancelled normally." It terminates *that* coroutine but is **not** treated as a failure: it does **not** cancel the parent, and does **not** cancel siblings. - **Anything else** (`IllegalStateException`, `IOException`, …) → a **failure** that propagates up the `Job` tree, cancelling parent and siblings, and is reported at the root. ## Where `CancellationException` comes from It is the in-band signal of *cooperative cancellation*. Suspending functions in `kotlinx.coroutines` (like `delay`, `yield`, `withContext`) check for cancellation and **throw `CancellationException`** at suspension points when the coroutine's `Job` is cancelled: ```kotlin val job = scope.launch { try { delay(10_000) // throws CancellationException when cancelled } finally { // runs during cancellation; suspending here needs withContext(NonCancellable) println("cleaning up") } } job.cancel() // requests cancellation ``` ## The classic swallowing bug ```kotlin // BAD: swallows cancellation try { delay(1000) } catch (e: Exception) { // also catches CancellationException! log.warn("oops", e) // coroutine now ignores its own cancellation } ``` Because `CancellationException` *is* an `Exception`, a broad catch eats it, and the coroutine keeps running as if it were never cancelled — defeating structured cancellation. Fixes: ```kotlin try { delay(1000) } catch (e: CancellationException) { throw e // always rethrow cancellation } catch (e: Exception) { handle(e) } ``` or cooperatively re-check: `coroutineContext.ensureActive()` re-throws if cancelled. ## Why this asymmetry exists Cancellation is *expected control flow* — a parent cancelling its children should not look like a crash. Treating `CancellationException` as a failure would mean cancelling one child fails the whole tree, which is exactly the opposite of what cancellation is for. So the framework hard-codes the distinction. ## API detail In Kotlin, `kotlinx.coroutines.CancellationException` is a `typealias` for `java.util.concurrent.CancellationException`. You can also throw your own subclass to carry a cause; it is still treated as cancellation.
- What goes wrong if you catch and swallow `CancellationException`?The coroutine ignores its own cancellation and keeps running, breaking structured concurrency — its parent thinks it cancelled, but the work continues.
- How do you suspend safely in a `finally` block during cancellation?Wrap the suspending cleanup in `withContext(NonCancellable) { ... }`, otherwise the suspension immediately throws `CancellationException` again.
A player tapping out of a game (cancellation) ends only their turn; a player flipping the table (failure) ends the whole game for everyone.
saying these in an interview costs you the question
- Saying CancellationException propagates up and cancels siblings
- Catching it in a broad catch and not rethrowing
- Thinking cancellation is a failure/crash of the whole scope
- Unaware that suspension points are where it is thrown