skip to content

Explain the difference between advanceUntilIdle(), advanceTimeBy(ms), and runCurrent(). When would you reach for each?

level: middleimportance: must knowfreq 75%

answer

  1. runCurrent = now only, clock stays
  2. advanceTimeBy = move clock, window is exclusive at the end
  3. advanceUntilIdle = play to the end
  4. after advanceTimeBy, runCurrent flushes the boundary task
  5. currentTime tells you how far you moved

basics

~10 s

advanceUntilIdle runs all pending work to completion. advanceTimeBy moves the clock a fixed amount and runs what falls in that window. runCurrent runs only tasks already due now, without moving the clock.

solid answer

~40 s

All three live on TestCoroutineScheduler (exposed via TestScope). runCurrent() executes every task scheduled at the current virtual time but does not advance the clock — useful to flush a just-launched coroutine to its first suspension point. advanceTimeBy(ms) moves currentTime forward by ms, executing tasks whose scheduled time falls within (current, current+ms]; in current kotlinx-coroutines it does NOT run tasks scheduled exactly at the new currentTime — you call runCurrent() after to flush those. advanceUntilIdle() repeatedly runs and advances until no scheduled tasks remain, jumping the clock to whatever time is needed; it's the 'fast-forward to the end' tool. Reach for runCurrent to assert immediate effects, advanceTimeBy to test behavior at a precise instant (e.g., 4s into a 5s debounce), and advanceUntilIdle to drain everything before final assertions.

code

kotlin · 17 lines
kotlin
@Test
fun threeDrivers() = runTest {
    val seen = mutableListOf<Int>()
    launch {
        delay(100); seen += 1
        delay(100); seen += 2
    }
    runCurrent()                 // launches body, parks at delay(100)
    assertEquals(emptyList<Int>(), seen)

    advanceTimeBy(100); runCurrent()   // boundary task at t=100 flushed
    assertEquals(listOf(1), seen)

    advanceUntilIdle()           // run the rest
    assertEquals(listOf(1, 2), seen)
    assertEquals(200, currentTime)
}

go deeper

for a junior

Knows advanceUntilIdle drains pending work and is the common default.

for a middle

Distinguishes all three accurately and picks the right one per scenario.

for a senior

Knows the advanceTimeBy boundary nuance and the infinite-reschedule hang risk.

for a principal

Reasons about deterministic test design, when manual stepping beats auto-advance, and how to structure tests of unbounded streams.

## The three drivers All are methods on `TestCoroutineScheduler`, surfaced on `TestScope` so you can call them directly inside `runTest`. ### runCurrent() Runs **all tasks scheduled at the current virtual time** and does **not** move the clock. A freshly `launch`ed coroutine is queued at the current time; `runCurrent()` lets it execute up to its first real suspension (e.g., its first `delay`). Use it to observe immediate side effects without skipping any delay. ### advanceTimeBy(delayTimeMillis) Moves `currentTime` forward by the given amount, executing every task whose scheduled virtual time lies in the half-open window `(oldTime, oldTime + ms)`. **Important nuance (current behavior):** a task scheduled *exactly* at the new `currentTime` is **not** run by `advanceTimeBy` — it's left pending. Call `runCurrent()` afterward to flush it. This is the precise tool for asserting state at a specific instant. ```kotlin val log = mutableListOf<String>() launch { delay(1000); log += "a" delay(1000); log += "b" } advanceTimeBy(1000) // window (0,1000), task at exactly 1000 NOT run yet assertEquals(emptyList<String>(), log) runCurrent() // now runs the task due at 1000 assertEquals(listOf("a"), log) ``` ### advanceUntilIdle() Runs and advances repeatedly until the scheduler has **no remaining scheduled tasks**, jumping `currentTime` forward as required. It's 'play to the end'. After it returns, everything that was going to happen has happened (unless a task reschedules forever, which would loop). ## Choosing one - **runCurrent()** — flush work due *now*; assert immediate effects; don't skip delays. - **advanceTimeBy(ms)** — assert behavior at a precise moment (mid-debounce, just-before vs just-after a timeout). - **advanceUntilIdle()** — drain the world before final assertions; the common default when you only care about the end state. ## Reading the clock Use `currentTime` (or `testScheduler.currentTime`) to verify how far virtual time moved — e.g., after `advanceUntilIdle()` it equals the time of the last completed task.

  • After advanceTimeBy(1000), why might a coroutine's delay(1000) not have resumed?
    advanceTimeBy runs tasks in (old, old+ms) — a task scheduled exactly at the new currentTime sits at the boundary and isn't executed. Follow with runCurrent() (or advance a hair further) to flush it.
  • Could advanceUntilIdle() hang?
    If a coroutine reschedules itself endlessly (e.g., an infinite loop of delay), there's never an idle state, so advanceUntilIdle would loop forever. Such code needs a different testing strategy (collect N items, then cancel).

saying these in an interview costs you the question

  • Says advanceTimeBy runs the boundary task scheduled exactly at the new time
  • Thinks runCurrent advances the clock
  • Uses advanceUntilIdle when needing to assert mid-flight intermediate state
  • Cannot explain that all three live on TestCoroutineScheduler
  • Confuses advanceUntilIdle with simply yielding once

context