skip to content

What is the difference between scope.cancel() and scope.coroutineContext.cancelChildren() for a scope you own, and when would you choose each?

level: middleimportance: should knowfreq 40%

answer

  1. cancel() = kill the Job, scope unusable after
  2. cancelChildren() = abort work, keep scope alive
  3. Relaunch after cancel() silently no-ops
  4. cancel() for teardown, cancelChildren() for reset
  5. Both raise CancellationException in children

basics

~10 s

cancel() cancels the scope's Job itself, so the scope is dead and can't be reused. cancelChildren() stops the current coroutines but leaves the Job alive, so you can launch new work afterward.

solid answer

~40 s

scope.cancel() cancels the scope's own Job. The Job transitions to cancelled permanently, all children are cancelled, and any future launch in this scope starts an already-cancelled coroutine — the scope is single-use and must be recreated. scope.coroutineContext.cancelChildren() cancels only the children of the scope's Job while leaving that Job active; the scope remains usable and you can launch fresh coroutines. Choose cancel() for true end-of-life teardown (component destroyed) — it's the leak-prevention call. Choose cancelChildren() when you want to abort current work but keep the scope (e.g. cancel an in-flight search before starting a new one, or reset between requests on a reusable owner). A subtle gotcha: calling cancel() on a scope and then trying to launch in it silently no-ops, which surprises people who reuse a scope across lifecycle restarts.

code

kotlin · 9 lines
kotlin
val scope = CoroutineScope(SupervisorJob() + Dispatchers.Default)

// Reset pattern: stop current, keep scope
scope.coroutineContext.cancelChildren()
scope.launch { /* runs fine, scope still Active */ }

// Teardown pattern: end of life
scope.cancel()
scope.launch { /* never runs - scope's Job is Cancelled */ }

go deeper

for a junior

Knows cancel() stops everything; may not know cancelChildren() keeps the scope reusable.

for a middle

Explains terminal vs. non-terminal cancellation and picks the right call for teardown vs. reset.

for a senior

Anticipates the silent-no-op-after-cancel bug and designs reusable-scope vs. recreate-scope strategies intentionally.

for a principal

Codifies lifecycle conventions (one cancel at teardown, cancelChildren for resets) so teams avoid scope-reuse pitfalls.

## Two different targets Both calls cancel coroutines, but they target different things: - `scope.cancel()` cancels the **scope's own Job** (and therefore its children). - `scope.coroutineContext.cancelChildren()` cancels the **children of** the scope's Job, leaving the Job itself active. ## scope.cancel() — terminal This is a method on `CoroutineScope` (via the `Job` in its context). After it: - The scope's `Job` is in the **cancelled** (terminal) state. - All children receive `CancellationException` and stop at their next suspension/cancellation check. - **You cannot reuse the scope.** A later `scope.launch { }` returns a coroutine that is immediately cancelled and never runs. There is no 'reopen'. Use it exactly once, at end-of-life: `onCleared()`, `close()`, `@PreDestroy`, end of a request. This is the canonical leak-prevention call. ## cancelChildren() — reset, keep the scope ```kotlin fun restartWork() { scope.coroutineContext.cancelChildren() // abort current children scope.launch { freshWork() } // scope still alive } ``` The scope's `Job` stays `Active`, so new launches work. This is ideal for 'replace the current operation' patterns or reusing a single long-lived scope across multiple work cycles. ## Why the distinction matters for lifecycle Many lifecycle bugs come from mixing these up: - Calling `cancel()` and then expecting to relaunch -> coroutines silently don't run. - Calling `cancelChildren()` at true teardown -> the scope's Job lives on; if it was a child of some larger Job it stays attached, and you haven't fully torn down ownership. ## A note on cancel(cause) `cancel()` can take a `CancellationException` cause for diagnostics: `scope.cancel(CancellationException("screen closed"))`. The cause shows up in the children's cancellation. It does not change the terminal nature of the call. ## Key APIs/keywords - `CoroutineScope.cancel()` — cancels the scope's Job; terminal. - `Job.cancelChildren()` (reached via `coroutineContext.cancelChildren()`) — cancels children only; non-terminal. - `CancellationException` — the exception used to signal cancellation; normal and not an error. - `Job` states: `Active`, `Cancelling`, `Cancelled`, `Completed`.

  • After scope.cancel(), what happens if you call scope.launch { }?
    The launched coroutine starts already cancelled and its body never executes; the scope cannot be revived.
  • Which call would you use to cancel an in-flight search when the user types a new query into the same reusable scope?
    cancelChildren() — it aborts the current search but keeps the scope active so the new search can launch.

saying these in an interview costs you the question

  • Thinks a scope can be reused after cancel()
  • Uses cancelChildren() for final teardown and assumes ownership is released
  • Cannot explain why a relaunch after cancel does nothing
  • Believes the two calls are interchangeable
  • Treats CancellationException as a real error to log loudly

context