skip to content

What is suspendCancellableCoroutine and when do you use it?

level: juniorimportance: must knowfreq 70%

answer

  1. Callback API -> suspend function bridge
  2. resume = return, resumeWithException = throw
  3. invokeOnCancellation = cleanup
  4. Resume exactly once
  5. Cancellable variant is cancellation-aware

basics

~10 s

It is a helper that turns an old-style callback API into a suspend function. You pause the coroutine, then call resume in the callback to continue with a result, or resumeWithException to fail.

solid answer

~40 s

suspendCancellableCoroutine is a suspending builder from kotlinx.coroutines that bridges callback- or future-based APIs into idiomatic suspend functions. It suspends the calling coroutine and hands you a CancellableContinuation. You register a callback; when it fires you call continuation.resume(value) to deliver a result or continuation.resumeWithException(t) to deliver an error, which respectively returns from or throws out of the suspend function. Unlike the lower-level suspendCoroutine, it is cancellation-aware: if the surrounding coroutine is cancelled while suspended, the continuation is resumed with a CancellationException, and you can register continuation.invokeOnCancellation { ... } to tear down the underlying resource (unregister listener, cancel the request). You should resume the continuation exactly once.

code

kotlin · 9 lines
kotlin
suspend fun await(call: Call): Response =
    suspendCancellableCoroutine { cont ->
        call.enqueue(object : Callback {
            override fun onResponse(r: Response) = cont.resume(r)
            override fun onFailure(e: IOException) =
                cont.resumeWithException(e)
        })
        cont.invokeOnCancellation { call.cancel() }
    }

go deeper

for a junior

Knows it bridges a callback into a suspend function and that resume continues with a result.

for a middle

Can write the lambda correctly, uses resumeWithException for errors, and adds invokeOnCancellation.

for a senior

Explains single-resume invariant, cancellation semantics, and when suspendCoroutine is acceptable.

for a principal

Discusses thread-safety of resume, races between callback and cancellation, and designing reusable bridge helpers.

## The problem it solves Many Java/Android/SDK APIs are **callback-based** or return a **future**: you call a method and later get notified via a listener. Coroutines want plain `suspend` functions that *return a value* or *throw*. `suspendCancellableCoroutine` is the official bridge between the two worlds. ## How it works `suspendCancellableCoroutine { cont -> ... }` is itself a `suspend` function. When called it: 1. **Suspends** the current coroutine. 2. Gives you a `CancellableContinuation<T>` (`cont`) inside the lambda. 3. Inside the lambda you **start the async work** and register a callback. 4. The coroutine stays suspended until you **resume** the continuation. You resume in one of two ways: - `cont.resume(value)` — the suspend function *returns* `value`. - `cont.resumeWithException(throwable)` — the suspend function *throws* `throwable`. Resume the continuation **exactly once**. A second resume throws `IllegalStateException`. ## Cancellation support The "Cancellable" prefix matters. If the surrounding coroutine is cancelled while suspended, the continuation completes with a `CancellationException` automatically. You register cleanup with: ```kotlin cont.invokeOnCancellation { underlyingCall.cancel() } ``` This runs on cancellation so you can release the resource you started. ## Example ```kotlin suspend fun fetchUser(api: UserApi): User = suspendCancellableCoroutine { cont -> val call = api.getUser(object : Callback { override fun onSuccess(u: User) = cont.resume(u) override fun onError(e: Throwable) = cont.resumeWithException(e) }) cont.invokeOnCancellation { call.cancel() } } ``` ## suspendCoroutine vs suspendCancellableCoroutine `suspendCoroutine` (from the Kotlin standard library) does the same bridging but is **not** cancellation-aware: a cancelled coroutine cannot interrupt it and there is no `invokeOnCancellation`. Prefer `suspendCancellableCoroutine` from kotlinx.coroutines for real work; use `suspendCoroutine` only when cancellation is genuinely impossible.

  • What happens if you call resume twice?
    The second resume throws IllegalStateException; a continuation may be resumed only once.
  • Why prefer the cancellable variant over suspendCoroutine?
    It propagates cancellation of the parent coroutine and lets you clean up the underlying resource via invokeOnCancellation.

Like leaving a phone number with a service: you hang up (suspend) and they call you back (resume) with the answer or the bad news.

saying these in an interview costs you the question

  • Says resume returns immediately like a normal function call
  • Forgets that resumeWithException is how you surface errors
  • Resumes the continuation multiple times
  • Confuses it with launch/async coroutine builders
  • Ignores cancellation cleanup entirely

context