skip to content

suspendCancellableCoroutine Callback Bridge

suspendCancellableCoroutine turns a callback or future API into a suspend function: you resume with a value or an exception, and register cleanup for cancellation. Interviewers ask for it whenever the scenario involves an old-style asynchronous library.

part ofKotlinoverview, primer and where to startread it →
on this pageshow

questions

5

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

open as a page

What is invokeOnCancellation for, and what are the rules around resuming after cancellation?

level: middleimportance: must knowfreq 55%

basics

~10 s

invokeOnCancellation runs a cleanup block when the coroutine is cancelled while suspended, so you can stop the underlying work. After cancellation any value you try to resume with is ignored, so close it safely.

open as a page

Compare suspendCoroutine and suspendCancellableCoroutine. When is each appropriate?

level: middleimportance: should knowfreq 45%

basics

~10 s

Both turn a callback into a suspend function. suspendCoroutine cannot react to cancellation; suspendCancellableCoroutine can cancel the underlying work and clean up. Prefer the cancellable one for almost everything.

open as a page

Explain the thread-safety and single-resume guarantees of CancellableContinuation when bridging a callback that can fire from multiple threads.

level: seniorimportance: should knowfreq 30%

basics

~20 s

A continuation must be resumed exactly once. Resume is thread-safe, but if a callback might fire success and error, or fire twice, you must guard so only the first resume wins and you do not throw on the second.

open as a page

You are designing a reusable await() bridge for a Future/CompletableFuture-style API. What correctness concerns must the implementation address?

level: seniorimportance: nice to knowfreq 22%

basics

~10 s

Resume once from the completion handler with the result or the exception, cancel the underlying future in invokeOnCancellation, unwrap wrapper exceptions, and make sure a result arriving after cancellation does not leak.

open as a page