Coroutines Basics Quiz

KOTLIN › Coroutines

What best describes what happens when a coroutine hits a suspension point inside a suspend function?

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?

Answer: async

async returns Deferred<T> and you call await() to get the value; launch returns a Job with no result.

Under the hood, how does the Kotlin compiler implement suspend functions?

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.

How does delay differ from Thread.sleep inside a coroutine?

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.

From where can a suspend function be called?

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.

What is the intended use of runBlocking?

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 function should NOT be marked suspend?

Answer: suspend fun formatName(first: String, last: String): String which returns "$first $last" and calls no suspending functions

formatName does pure string concatenation -- nothing inside it suspends. Marking it suspend adds coroutine machinery overhead for no benefit. The others genuinely suspend: fetchPosts waits for a network response, readFile uses withContext, and delay500 calls delay.

What does a Job represent?

Answer: The lifetime of running work, with handles to cancel it and wait for it

A Job is about lifetime: started, finishing, cancelled. Deferred is the variant that also carries a result, which is why it extends Job rather than replacing it.

Immediately after job.cancel(), job.isCompleted is false. Why?

Answer: The job is Cancelling: it still has to unwind and wait for its children

Cancellation is cooperative and asynchronous. The job passes through Cancelling while finally blocks run and children unwind, and only then reaches Cancelled, where isCompleted becomes true.

Which is true of a cancelled Job?

Answer: isCompleted is true and isCancelled is true

A cancelled coroutine is finished, just not successfully. Using isCompleted alone to mean success is therefore a bug: pair it with isCancelled to tell them apart.

How do join() and await() differ on failure?

Answer: join() returns normally regardless; await() rethrows the coroutine exception

join waits for completion however it completed, so it stays silent. await waits for a value and therefore has to surface the failure, which is why the same coroutine can look quiet or loud depending on which you called.

Roughly how long does this take, given each call takes 300ms?

Answer: About 600ms, because awaiting on the same line makes the calls sequential

The second async is not created until the first await returns, so nothing overlaps. Concurrency requires starting both deferreds before awaiting either.

Which statement about suspend fun is correct?

Answer: It is a function, not a coroutine: it has no Job and nothing to cancel

Only builders create coroutines. A suspend function runs inside its caller, on its caller dispatcher, and completes before the caller continues, which is why it has no lifetime of its own.

What is wrong with this refresh function?

Answer: The hand-built scope has no lifetime, so nothing cancels the work it starts

The caller has no idea this scope exists and nothing cancels it, so syncEverything outlives the screen. coroutineScope inherits the calling job instead, so cancellation propagates and the function waits for its children.

Why is GlobalScope.launch discouraged on Android?

Answer: It has no lifetime, so nothing cancels the work and it outlives the screen

Every structured-concurrency guarantee is switched off: the work is unowned, keeps its captured references alive, and carries on after the user has moved on. That is why it is marked DelicateCoroutinesApi.

Work must outlive a screen but not process death. What should own it?

Answer: An injected application-scoped CoroutineScope built on a SupervisorJob

An application scope gives the lifetime you want while staying cancellable, injectable and testable. WorkManager is the answer only when the work must survive the process being killed.

Back to Coroutines Basics