What is suspendCancellableCoroutine and when do you use it?
answer
- Callback API -> suspend function bridge
- resume = return, resumeWithException = throw
- invokeOnCancellation = cleanup
- Resume exactly once
- Cancellable variant is cancellation-aware
basics
~10 sIt 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 ssuspendCancellableCoroutine 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 linessuspend 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
Knows it bridges a callback into a suspend function and that resume continues with a result.
Can write the lambda correctly, uses resumeWithException for errors, and adds invokeOnCancellation.
Explains single-resume invariant, cancellation semantics, and when suspendCoroutine is acceptable.
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