skip to content

Suspension Mechanics

What the suspend keyword actually means, how the compiler turns suspension points into continuation-passing state machines, and how you wrap an existing callback API as a suspend function. This is the mechanism half of coroutines, and interviewers use it to separate users from understanders.

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

explore

questions

15

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 does the Kotlin compiler actually do to a `suspend` function under the hood? Describe the Continuation parameter it adds.

level: juniorimportance: must knowfreq 70%

basics

~20 s

The compiler rewrites a suspend function to take a hidden extra parameter representing 'what to do next'. When the function pauses, it hands work back to that callback and resumes later instead of blocking a thread.

open as a page

What does the `suspend` modifier do, and where can you call a `suspend` function from?

level: juniorimportance: must knowfreq 85%

basics

~10 s

suspend marks a function that can pause and resume later without blocking its thread. You can only call it from a coroutine or from another suspend function.

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

How does the compiler use a state machine and a `label` field to resume a suspend function with multiple suspension points?

level: middleimportance: must knowfreq 60%

basics

~20 s

The compiler splits the function at each pause point into numbered states. A label counter remembers which state to run next, so when the function resumes it jumps straight to the right spot instead of starting over.

open as a page

What is the difference between `delay()` and `Thread.sleep()` inside a coroutine?

level: middleimportance: must knowfreq 80%

basics

~10 s

delay() pauses the coroutine without blocking the thread, so other coroutines can use it. Thread.sleep() blocks the whole thread, freezing everything scheduled on it.

open as a page

Are `suspend` function calls concurrent or sequential by default? Show how to actually run two suspend calls in parallel.

level: middleimportance: must knowfreq 65%

basics

~10 s

By default suspend calls run one after another (sequentially). To run them in parallel you start each in its own coroutine, e.g. with async, then await both.

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

Where are a suspend function's local variables stored across a suspension, and what is the `$continuation` / `ContinuationImpl` object?

level: middleimportance: should knowfreq 45%

basics

~10 s

Locals that must survive a pause are saved as fields on a generated continuation object instead of living on the stack. That object also holds the label, so resuming restores everything needed to continue.

open as a page

What exactly does "suspend without blocking a thread" mean, and how does it improve scalability over thread-per-request blocking?

level: middleimportance: should knowfreq 60%

basics

~20 s

Suspending pauses a coroutine but hands its thread back to do other work, so one thread serves many waiting tasks. Blocking keeps the thread idle and busy, so you need one thread per waiting task.

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

Trace how an exception thrown by an awaited operation reaches the suspend function that awaited it, in terms of `resumeWith`, `Result`, and the state machine.

level: seniorimportance: should knowfreq 30%

basics

~20 s

Failures travel through the same resume channel as values. The continuation is resumed with a failed Result; when the state machine re-enters, it unwraps that Result, which re-throws the exception right at the line that was awaiting.

open as a page

A teammate claims 'every suspend call allocates objects and is expensive.' Using your knowledge of the CPS state-machine transform, when is that true and when is it false?

level: seniorimportance: should knowfreq 35%

basics

~10 s

It's only partly true. If a suspend call finishes without pausing, it runs almost like a normal function call with no extra allocation. Allocation and real cost happen only when it actually suspends.

open as a page

When designing an API, how do you decide whether a function should be `suspend`, and what are the consequences of adding or removing the modifier?

level: seniorimportance: should knowfreq 45%

basics

~20 s

Mark a function suspend if it may need to wait (IO, delays, other suspend calls) without blocking. Adding or removing suspend is a breaking change to callers because it changes where the function can be called from.

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