Coroutines Under the Hood Flashcards

KOTLIN › Coroutines

What does the compiler add to every suspend fun signature?
A hidden final Continuation parameter, and it widens the return type to Object so the function can return either the value or the COROUTINE_SUSPENDED sentinel. That is why suspend functions can only be called from other suspend functions.
What is a Continuation?
The rest of the function reified as an object: a CoroutineContext plus a single resumeWith(Result<T>) method. Because Result carries a value or an exception, resuming can either continue normally or throw at the suspension point.
Where does a suspended coroutine live?
On the heap, as a Continuation object referenced by whatever it is waiting on. Not on a thread stack, which is precisely why the thread is free to run other work.
Why do locals that survive a suspension point become fields?
Because suspension is a real return, so the stack frame is discarded. Anything needed after resumption must live on the heap-allocated continuation, which is also why a long-lived coroutine keeps large captured objects alive.
What is COROUTINE_SUSPENDED and why does it matter for performance?
A sentinel meaning "I did not finish, I kept your continuation". Returning anything else is the fast path: a suspend function that finds its value immediately never allocates a continuation, so the keyword costs almost nothing when it does not actually suspend.
How does a CoroutineDispatcher control which thread runs a coroutine?
By implementing ContinuationInterceptor and wrapping the continuation, so resumeWith packages the resumption as a Runnable and hands it to dispatch() instead of running inline. That is why a dispatcher acts only at resumption points.
Why can a CoroutineContext hold only one dispatcher?
Because a dispatcher is stored under the ContinuationInterceptor key rather than a key of its own, and plus replaces per key. Adding a second dispatcher replaces the first while leaving CoroutineName and Job untouched.
How does structured concurrency fall out of CoroutineContext?
Job is a context element. A builder reads the Job from the scope context and makes it the parent of the new coroutine job. Waiting for children, transitive cancellation, and failure propagation are all consequences of that one link.
What does viewModelScope.launch(Job()) do?
Detaches the coroutine. The fresh Job replaces the scope job in the child context, so the coroutine has no parent and onCleared cancels a hierarchy it is not part of. It leaks exactly as viewModelScope exists to prevent.
What are the four CoroutineStart modes?
DEFAULT schedules immediately; LAZY waits for start(), join(), or await(); ATOMIC guarantees the body begins even if cancelled first; UNDISPATCHED runs on the current thread up to the first suspension point.
What is the risk of a LAZY coroutine nobody starts?
It is still an unfinished child in the job hierarchy, so an enclosing coroutineScope waits for it forever. The result is a hang, not a silent no-op.
Why prefer suspendCancellableCoroutine over suspendCoroutine?
Because suspendCoroutine cannot be cancelled: once suspended it waits for the callback regardless, so a cancelled parent hangs, and if the callback never fires the continuation leaks. The cancellable version also gives you invokeOnCancellation to abort the underlying operation.
Does suspend mean the function runs off the main thread?
No. It runs on the caller dispatcher. Nothing about the keyword moves work anywhere: only withContext and the builders change threads, and a CPU loop inside a suspend function called from Main blocks the main thread.
Do two suspending calls in a row run in parallel?
No, they run in sequence and the total time is the sum. Overlap requires async inside a scope, then awaiting both. The keyword only makes waiting cheap; concurrency comes from builders.
What does suspendCancellableCoroutine do, and what are its three responsibilities?
It bridges a callback-based API into a suspend function. Inside the lambda you must: (1) call cont.resume(value) on success, (2) call cont.resumeWithException(e) on failure, and (3) register cont.invokeOnCancellation { } to clean up the callback when the coroutine is cancelled.

Back to Coroutines Under the Hood