How does withContext(NonCancellable) interact with the coroutine context and Job hierarchy? What is and isn't suppressed inside the block?
answer
- Only the Job element is swapped
- Dispatcher inherited -> same thread
- Parent cancel link broken in block
- Inner withTimeout still throws
- Non-cancellation exceptions still propagate
basics
~10 sInside the block, only the Job is swapped for NonCancellable, so normal cancellation is ignored. The dispatcher and other context stay the same, and timeout-based cancellation (withTimeout) inside the block still works.
solid answer
~40 swithContext(NonCancellable) overrides exactly one context element: the Job. NonCancellable is an always-active Job that breaks the parent-child link for cancellation, so external cancel() no longer propagates suspension throws into the block. Everything else — Dispatcher, CoroutineName, exception handler — is inherited unchanged, so you stay on the same thread pool. Because the link is broken, the block is structurally detached from cancellation: it won't be cancelled by the parent, but it also won't auto-cancel children based on the parent. A nested withTimeout still works because it creates its own child Job that throws TimeoutCancellationException independent of the suppressed outer Job. Exceptions other than CancellationException still propagate normally out of the block.
go deeper
Knows it makes cleanup uncancellable but may not know which context element changes.
States that only the Job is replaced and the dispatcher is inherited.
Explains the broken parent-child link, that inner timeouts still fire, and that ordinary exceptions still propagate.
Reasons about structured-concurrency invariants, detachment risks, and designs cleanup to stay bounded and observable under detachment.
## Coroutine context refresher A coroutine's **context** is an indexed set of elements: a `Job` (lifecycle/cancellation), a `CoroutineDispatcher` (which thread), a `CoroutineName`, a `CoroutineExceptionHandler`, etc. `withContext(x)` produces a context that is `currentContext + x`, where `+` replaces same-keyed elements. ## What NonCancellable replaces `NonCancellable` is an `object : AbstractCoroutineContextElement(Job), Job` — i.e. it occupies the **Job key**. So `withContext(NonCancellable)` replaces **only** the Job inside the block. The dispatcher stays the same, so you do **not** switch threads. Name and handler are inherited. ```kotlin withContext(NonCancellable) { // same dispatcher/thread as outside // Job is now NonCancellable (always active) } ``` ## What is suppressed - **External cancellation**: `parent.cancel()` makes the outer Job Cancelling, but suspension points inside the block consult `NonCancellable`, which is active, so they don't throw. The block runs to completion. - The parent→child cancellation link is **broken** for this block: it is structurally detached from the cancellation that the parent would normally deliver. ## What is NOT suppressed - **Timeout cancellation you install inside**: `withTimeout`/`withTimeoutOrNull` start a *new* child scope with its *own* Job and timer; that Job can still throw `TimeoutCancellationException`, so cleanup remains bounded. - **Other exceptions**: a `RuntimeException`/`IOException` thrown inside still propagates out normally. - **Dispatcher behavior**: still subject to the same threading. ## Pitfall: detachment Because NonCancellable detaches from the parent Job, code inside is no longer covered by structured concurrency's cancellation. That's the whole point for cleanup, but it means: don't launch long-lived children there, and don't rely on the parent to tear them down. ```kotlin withContext(NonCancellable) { // BAD: this child is detached from outer cancellation // launch { foreverLoop() } flushBuffers() // GOOD: bounded, finishes } ``` ## Summary table - Job: replaced by NonCancellable (active) — cancellation suppressed. - Dispatcher: inherited — same thread pool. - Inner withTimeout: still effective. - Non-cancellation exceptions: still propagate.
- If you launch a child coroutine inside withContext(NonCancellable), is it cancelled when the parent is cancelled?Not via the suppressed outer Job — the block is detached from the parent's cancellation. The child is parented to NonCancellable, so it won't receive the parent's cancellation; avoid spawning long-lived children there.
- Does NonCancellable change which thread the cleanup runs on?No. It only replaces the Job. The dispatcher is inherited, so the cleanup runs on the same dispatcher/thread pool as the surrounding code.
saying these in an interview costs you the question
- Thinks NonCancellable also overrides the dispatcher
- Believes withTimeout cannot work inside a NonCancellable block
- Claims all exceptions (including normal ones) are swallowed inside
- Assumes children launched inside still follow parent cancellation
- Confuses suppressing CancellationException with catching it