What CoroutineContext does coroutineScope/supervisorScope use, and how does the Job they install affect cancellation of children?
answer
- context inherited, Job replaced
- installed Job = parent of all children
- cancel scope -> cancel all children
- scope completes after children complete
- dispatcher unchanged; use withContext to switch
basics
~10 sBoth reuse the surrounding context (like the dispatcher) but add their own parent Job. Because that Job is the parent of every child, cancelling the scope cancels all the children inside it.
solid answer
~30 sBoth builders inherit the calling coroutine's CoroutineContext — dispatcher, CoroutineName, CoroutineExceptionHandler — and override only the Job element. coroutineScope installs a plain Job; supervisorScope installs a SupervisorJob. That installed Job becomes the parent of every coroutine started inside the block, which is what gives structured concurrency: cancelling the scope (or the outer coroutine) cancels all children, and the scope won't complete until children do. The difference between the two is solely the upward failure-propagation rule of the Job. You don't change the dispatcher with these builders — for that you use withContext.
go deeper
Knows context is inherited and the scope waits for children.
Explains that only the Job is replaced and that the Job parents the children, enabling downward cancellation.
Connects inherited dispatcher/name/handler plus replaced Job to structured-concurrency guarantees and the withContext combo.
Discusses designing suspend APIs whose internal scopes guarantee caller-driven cancellation and resource cleanup.
## Context inheritance A `CoroutineContext` is a set of elements keyed by type: the **dispatcher** (`CoroutineDispatcher`, e.g. `Dispatchers.IO`), a `CoroutineName`, a `CoroutineExceptionHandler`, and the **`Job`**. When you call `coroutineScope` or `supervisorScope`, the new scope's context is `parentContext + newJob` — every element is inherited **except the Job, which is replaced**. ```kotlin withContext(Dispatchers.IO + CoroutineName("loader")) { coroutineScope { // still on Dispatchers.IO, still named "loader", // but with a fresh child Job launch { /* inherits IO + name */ } } } ``` ## The installed Job and parent-child links The Job the builder installs becomes the **parent** of any coroutine started inside the block (`launch`, `async`). This parent-child link is the machinery of structured concurrency: - **Cancellation flows down**: cancelling the scope's Job cancels every child. If the *outer* coroutine that called the builder is cancelled, the builder's Job is cancelled too, so all children stop. - **Completion waits up**: the builder's Job does not complete until all children complete, which is why the suspend function only resumes after the last child. ## coroutineScope Job vs supervisorScope SupervisorJob Both give you the *downward* cancellation and the *wait-for-children* behavior identically. The **only** difference is upward failure propagation: - Plain `Job` (coroutineScope): a failed child cancels the parent Job → cancels siblings. - `SupervisorJob` (supervisorScope): a failed child does **not** cancel the parent → siblings survive. ## These builders don't switch dispatchers Neither builder changes the dispatcher; they keep the inherited one. To run a block on a different dispatcher use `withContext(Dispatchers.X)`. A common pattern combines them: ```kotlin withContext(Dispatchers.Default) { coroutineScope { /* CPU work, fail-fast children */ } } ``` ## Why this matters Understanding that only the Job is replaced explains why cancellation, naming, and dispatcher 'just work' inside these scopes, and why placing a `coroutineScope` boundary makes a suspend function automatically cancel its internal work if the caller is cancelled.
- If the coroutine that called coroutineScope is cancelled, what happens to children started inside?They are cancelled too. Cancelling the outer coroutine cancels the scope's Job, which cancels all child coroutines.
- How would you run the scope's work on Dispatchers.IO?Wrap it: withContext(Dispatchers.IO) { coroutineScope { ... } }. The builders themselves don't change the dispatcher.
Inheriting the context but swapping the Job is like keeping the same office and team name but appointing a new manager who tracks everyone's tasks.
saying these in an interview costs you the question
- Saying coroutineScope changes the dispatcher
- Thinking children get a brand-new unrelated context
- Not knowing the installed Job parents the children
- Claiming cancellation does not reach children