skip to content

Compare isActive, ensureActive() and yield() for making a loop cancellable. When would you pick each?

level: middleimportance: must knowfreq 60%

answer

  1. isActive = Boolean, no throw
  2. ensureActive = throws if cancelled
  3. yield = throws + reschedules
  4. yield for fairness, others for hot loops
  5. yield is more expensive

basics

~20 s

isActive is a Boolean you test to exit the loop without throwing. ensureActive() throws CancellationException if cancelled. yield() throws too and also lets other coroutines run. Pick by whether you want to throw and/or reschedule.

solid answer

~40 s

All three read the same cancellation state. **isActive** is a non-throwing Boolean extension on CoroutineScope/CoroutineContext; `while (isActive)` exits cleanly and your code after the loop still runs normally — good when you want a graceful, non-exceptional stop. **ensureActive()** checks and throws CancellationException immediately if cancelled — concise when you want the coroutine to unwind like other suspend calls, and it works even where you only have a CoroutineContext/Job. **yield()** is a suspend function: it throws on cancellation AND suspends to give the dispatcher a chance to run other coroutines, preventing one CPU loop from starving the thread pool. Cost ordering: isActive ≈ ensureActive() are cheap field reads; yield() is more expensive because it actually reschedules. Use isActive/ensureActive in hot loops, yield() when fairness matters (e.g., one Default thread, many coroutines).

code

kotlin · 9 lines
kotlin
// Fairness + cancellation in a shared-thread CPU loop
launch(Dispatchers.Default) {
    var n = 0
    while (true) {
        if (n % 1000 == 0) yield()  // periodic reschedule + cancel check
        else ensureActive()         // cheap check the rest of the time
        n++
    }
}

go deeper

for a junior

Knows isActive stops a loop; may not distinguish throwing vs non-throwing.

for a middle

Correctly contrasts throw/non-throw and that yield reschedules; picks the right tool per scenario.

for a senior

Adds cost/fairness reasoning, periodic-yield patterns, and the isActive-outside-context pitfall.

for a principal

Frames dispatcher fairness and starvation trade-offs, and policy for compute coroutines across a limited Default pool.

## The three tools They all consult the same `Job` cancellation state but differ in behavior. ### `isActive` - An extension property returning `Boolean`, available on `CoroutineScope` and on `CoroutineContext` (via the `Job`). - `true` while active, `false` after cancellation/completion. - **Does not throw.** You choose what to do. ```kotlin while (isActive) { step() } // reaches here normally on cancel; you can run cleanup without a try/catch ``` ### `ensureActive()` - A function on `CoroutineScope`/`CoroutineContext`/`Job`. - If cancelled, **throws** the Job's `CancellationException`; otherwise returns. - Concise for the common case of "abort immediately if cancelled". ```kotlin while (true) { ensureActive(); step() } ``` ### `yield()` - A **`suspend`** function. - Throws `CancellationException` if the coroutine is cancelled, **and** suspends so other coroutines queued on the dispatcher can run, then resumes. - Use when a long CPU loop shares a thread (or limited pool) with other coroutines and you want **fairness** — without it, one greedy loop can monopolize a `Dispatchers.Default` thread. ```kotlin while (true) { yield(); step() } // also a suspension point ``` ## Choosing | Need | Pick | |------|------| | Graceful exit, no exception | `isActive` | | Throw/unwind immediately, terse | `ensureActive()` | | Throw AND let others run (fairness) | `yield()` | ## Cost & gotchas - `isActive`/`ensureActive()` are essentially atomic state reads — cheap, safe to call every iteration. - `yield()` performs a real reschedule; calling it on every tiny iteration adds overhead. For very hot loops, yield every N iterations. - `isActive` only reflects **this** coroutine's context; reading it outside a coroutine context returns `true` (no Job present), which can hide bugs. - Never swallow the `CancellationException` thrown by `ensureActive()`/`yield()`; rethrow it.

  • Why not call yield() on every single iteration?
    yield() actually reschedules through the dispatcher, which is far costlier than a state read. On hot loops that adds significant overhead; yield periodically (every N iterations) instead.
  • If isActive doesn't throw, how does the coroutine end up cancelled?
    You exit the loop yourself; the coroutine then completes normally. If a parent observes a cancel it may still propagate, but the loop's own stop is non-exceptional.

saying these in an interview costs you the question

  • Saying all three behave identically
  • Claiming isActive throws CancellationException
  • Not knowing yield() also reschedules / aids fairness
  • Recommending yield() in every iteration of a hot loop
  • Suggesting Thread.sleep() instead of yield/delay

context