Coroutines Basics Quiz
KOTLIN › Concurrency
What best describes what happens when a coroutine hits a suspension point inside a suspend function?
- The thread blocks until the operation finishes and then resumes
- The coroutine is moved into a new process to continue running
- The coroutine suspends, and the thread is freed for other work
- The JVM creates a fresh OS thread for that coroutine
Answer: The coroutine suspends, and the thread is freed for other work
Suspension pauses the coroutine without blocking; the underlying thread is released to do other work and the coroutine resumes later.
Which builder returns a Deferred that you await for a result?
- async
- launch
- runBlocking
- withContext
Answer: async
async returns Deferred<T> and you call await() to get the value; launch returns a Job with no result.
Which dispatcher is the correct default choice for CPU-intensive work like parsing or sorting a large list?
- Dispatchers.Main
- Dispatchers.IO
- Dispatchers.Unconfined
- Dispatchers.Default
Answer: Dispatchers.Default
Dispatchers.Default is backed by a pool sized to the number of CPU cores, intended for CPU-bound work; IO is for blocking I/O.
What is the primary purpose of withContext(Dispatchers.IO) inside a suspend function?
- To launch a new independent coroutine that is not cancelled with its parent
- To switch the block onto an I/O thread, keeping the function main-safe
- To block the main thread completely until the I/O operation finishes
- To convert a suspend function into an ordinary blocking function permanently
Answer: To switch the block onto an I/O thread, keeping the function main-safe
withContext switches to the specified dispatcher for the block and returns to the original context afterward, so blocking I/O runs off the main thread.
Under the hood, how does the Kotlin compiler implement suspend functions?
- By wrapping each suspending call inside a synchronized lock block automatically
- By scheduling each suspend function on its own dedicated background thread
- By a CPS transform into a state machine with a hidden Continuation parameter
- By using Java Virtual Threads exclusively under the hood for every call
Answer: By a CPS transform into a state machine with a hidden Continuation parameter
The compiler rewrites a suspend function using CPS, adding a Continuation parameter and compiling the body into a resumable state machine.
In structured concurrency, what happens to child coroutines when their CoroutineScope is cancelled?
- All child coroutines in that scope are cancelled too
- Children keep running until they finish on their own
- Only coroutines started with async are cancelled
- The children are detached and reparented to GlobalScope
Answer: All child coroutines in that scope are cancelled too
Cancellation propagates down the coroutine hierarchy: cancelling a scope (or its Job) recursively cancels all of its child coroutines.
What is the behavior of Dispatchers.Unconfined?
- It always runs the whole coroutine on the main thread, without changing
- It dispatches every resumption to one fixed background thread pool
- It guarantees thread confinement with FIFO ordering for all resumes
- It starts in the caller thread, then resumes where suspension decides
Answer: It starts in the caller thread, then resumes where suspension decides
Unconfined starts in the caller thread but after the first suspension point resumes on the thread determined by the suspending call; it is an advanced, rarely-needed dispatcher.
How does delay differ from Thread.sleep inside a coroutine?
- Both block the underlying carrier thread for the entire given duration
- Thread.sleep is the coroutine-friendly, non-blocking way to pause a job
- delay suspends without blocking, while Thread.sleep blocks the thread
- delay can only be called from inside a runBlocking builder block
Answer: delay suspends without blocking, while Thread.sleep blocks the thread
delay is a suspending function that releases the thread while the coroutine waits, whereas Thread.sleep blocks the thread for the whole duration, wasting it.
How does exception delivery differ between launch and async?
- Both encapsulate and rethrow the exception only at a join() or await() call
- Neither one propagates its uncaught exception up to the parent coroutine scope
- launch rethrows inside await(), while async just logs it to the console
- launch propagates to its scope immediately; async holds it for await()
Answer: launch propagates to its scope immediately; async holds it for await()
launch treats an uncaught exception as a failure and propagates it up the scope right away, while async stores it in the Deferred so it surfaces when you call await().
What distinguishes supervisorScope from coroutineScope?
- In supervisorScope, one child's failure won't cancel its siblings
- supervisorScope forces every child to run only on Dispatchers.IO
- supervisorScope cannot call suspend functions inside it at all
- supervisorScope blocks the caller thread until every child completes
Answer: In supervisorScope, one child's failure won't cancel its siblings
supervisorScope uses a SupervisorJob, so a child's failure is isolated and does not cancel its siblings, unlike coroutineScope where one failure cancels the whole scope.
From where can a suspend function be called?
- From any ordinary function, just like a normal call in Kotlin code
- Only from another suspend function, or inside a coroutine builder
- Only from functions marked with @Composable in a Compose UI layer
- Only from code running on Dispatchers.Default in the background
Answer: Only from another suspend function, or inside a coroutine builder
Suspend functions need a continuation, so they can only be invoked from another suspend function or from a coroutine started by a builder like launch, async, or runBlocking.
A coroutine runs a long CPU loop with no suspension points and does not stop when its scope is cancelled. Why?
- Cancellation never actually works for any coroutine running on Dispatchers.Default
- Coroutines simply cannot be cancelled at all once they have already started
- Cancellation is cooperative, so an unchecked loop never sees the cancelled state
- The coroutine must be launched inside GlobalScope to ever become cancellable
Answer: Cancellation is cooperative, so an unchecked loop never sees the cancelled state
Cancellation only sets a flag; cooperative code must hit a suspension point or check isActive/ensureActive/yield to actually react, otherwise a tight loop keeps running.
What is the intended use of runBlocking?
- To bridge blocking code to coroutines in main() or tests, blocking the thread
- To run a coroutine on the main thread without ever actually blocking it
- As the standard way to start coroutines from an Android Activity or ViewModel
- To switch dispatchers cleanly from inside a running suspend function
Answer: To bridge blocking code to coroutines in main() or tests, blocking the thread
runBlocking blocks the current thread until its body completes, which is useful for main functions and tests but is the wrong tool inside UI code, where it would freeze the thread.
Which statement about Dispatchers.IO is correct in modern kotlinx.coroutines?
- It is backed by just a single thread that serializes every I/O operation in strict order
- It is sized to the exact number of CPU cores, just like Dispatchers.Default
- It must never be used to make any network requests under any circumstances
- It shares Dispatchers.Default's pool but allows far more concurrent blocking tasks
Answer: It shares Dispatchers.Default's pool but allows far more concurrent blocking tasks
Dispatchers.IO and Dispatchers.Default draw from the same shared pool; IO allows a large number of concurrent blocking tasks by default, and limitedParallelism can constrain how many run at once.