skip to content

Coroutines

Kotlin's concurrency model: suspend functions that release the thread instead of blocking it, builders that start work, dispatchers that decide where it runs, and a Job hierarchy that makes cancellation and cleanup structural. Any backend or Android interview that touches async work spends most of its time here.

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

explore

questions

144 · 8 sections

What is a Kotlin coroutine, and how does it differ from a thread?

level: juniorimportance: must knowfreq 85%
basics
~10 s

A coroutine is a lightweight task that can pause and resume. Unlike a thread, it does not block an OS thread while waiting, so you can run very many of them at once cheaply.

open as a page

What is structured concurrency, and how does the parent Job/scope tie coroutine lifetimes together?

level: middleimportance: must knowfreq 72%
basics
~20 s

Structured concurrency means every coroutine runs inside a scope, and a parent waits for all its children to finish. If the parent is cancelled, its children are cancelled too — so nothing is left running or leaked.

open as a page

How does the Kotlin compiler implement a `suspend` function under the hood?

level: seniorimportance: must knowfreq 70%
basics
~20 s

The compiler rewrites a suspend function into a state machine. Each pause point becomes a state, and the function is given a hidden parameter that lets it remember where to continue from when it resumes.

open as a page

Why are coroutines described as 'cheap', and what does that buy you compared to a thread-per-task model?

level: middleimportance: should knowfreq 60%
basics
~10 s

Each coroutine is just a small object, not an OS thread, so creating one barely costs memory. That lets you have huge numbers of waiting tasks at once without exhausting threads.

open as a page

Why did Kotlin implement coroutines as a compiler transform plus a library rather than as runtime fibers or extra threads? What trade-offs does that bring?

level: principalimportance: nice to knowfreq 30%
basics
~20 s

Kotlin builds coroutines from a small compiler feature plus a library, instead of changing the runtime. This keeps them portable and cheap, but means they only pause at marked points and you must use suspend-aware libraries.

open as a page

What is suspendCancellableCoroutine and when do you use it?

level: juniorimportance: must knowfreq 70%
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.

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 does the async coroutine builder return, and how do you get the result out of it?

level: juniorimportance: must knowfreq 80%
basics
~10 s

async starts a background task and hands you a Deferred. You call await() on it to wait for and receive the result value.

open as a page

What does CoroutineScope.launch do, and what does it return?

level: juniorimportance: must knowfreq 85%
basics
~10 s

launch starts a new coroutine that runs in the background without giving you a result. It returns a Job, a handle you can use to wait for it to finish or to cancel it.

open as a page

What is runBlocking in Kotlin coroutines, and when should you use it?

level: juniorimportance: must knowfreq 80%
basics
~10 s

runBlocking is a bridge that lets regular blocking code call suspend functions. It blocks the current thread until the coroutines inside finish. Use it in main() and tests, not inside other coroutines.

open as a page

What does the suspending function coroutineScope do, and how is it different from launch or runBlocking?

level: juniorimportance: must knowfreq 70%
basics
~10 s

coroutineScope starts a block where you can run several coroutines and it waits for all of them to finish before returning. It does not block the thread; it only suspends.

open as a page

What does withContext do, and how would you use it to run blocking I/O off the main thread?

level: juniorimportance: must knowfreq 80%
basics
~20 s

withContext runs a block of suspending code on a different thread pool and waits for its result. You wrap blocking I/O in withContext(Dispatchers.IO) so it does not freeze the main thread, then it returns the value.

open as a page

What is a CoroutineContext in Kotlin coroutines, and what kind of data does it hold?

level: juniorimportance: must knowfreq 70%
basics
~20 s

It is a small set of settings carried by every coroutine. It bundles things like which thread pool runs the coroutine, its Job (lifecycle), and an optional name, so each coroutine knows how it should run.

open as a page

What is a CoroutineDispatcher, and when would you use Dispatchers.Default versus Dispatchers.IO?

level: juniorimportance: must knowfreq 85%
basics
~10 s

A dispatcher decides which thread a coroutine runs on. Use Default for heavy CPU work like sorting or parsing, and IO for waiting on network or disk calls.

open as a page

What does Dispatcher.limitedParallelism(n) do, and why would you use it?

level: juniorimportance: should knowfreq 45%
basics
~20 s

It creates a view of a dispatcher that lets at most n coroutines run at the same time. You use it to cap how much work hits a limited resource, like a database or an external API.

open as a page

Explain the Element/Key model of CoroutineContext: how does context[CoroutineName] resolve an element, and why is each element also a CoroutineContext?

level: middleimportance: should knowfreq 45%
basics
~20 s

Each setting in the context is an Element tagged with a unique Key. You look one up by indexing with its key. Every single element is itself a tiny one-item context, which is why you can add them together with +.

open as a page

How does the + operator combine coroutine contexts, and what are its precedence/replacement rules when the same kind of element appears twice?

level: middleimportance: should knowfreq 40%
basics
~20 s

The + operator merges two contexts into a new one. If both sides contain the same kind of element (say two dispatchers), the one on the right wins and replaces the one on the left.

open as a page

What is a Job in Kotlin coroutines, and what are the states in its lifecycle?

level: juniorimportance: must knowfreq 70%
basics
~10 s

A Job is a handle to a running coroutine. It can be active, can complete, or can be cancelled. You can wait for it, cancel it, or check if it is still running.

open as a page

What does the coroutineScope { } builder do, and how is it different from launching with GlobalScope?

level: juniorimportance: must knowfreq 65%
basics
~20 s

coroutineScope runs a block, starts child coroutines inside it, and waits until all of them finish before returning. GlobalScope launches coroutines that are not tied to anything and can outlive your code, so they can leak.

open as a page

Why does an object with a lifecycle (e.g. a ViewModel, a service, a request handler) usually own its own CoroutineScope, and what does cancelling that scope accomplish?

level: juniorimportance: must knowfreq 70%
basics
~20 s

The object creates a scope to launch background work tied to its life. When the object is done, it cancels the scope, which stops every coroutine started in it so nothing keeps running and leaking.

open as a page

What is structured concurrency in Kotlin coroutines, and what core guarantee does it give you?

level: juniorimportance: must knowfreq 80%
basics
~10 s

Every coroutine runs inside a scope, and that scope won't finish until all the coroutines it started have finished. This stops background work from being forgotten or leaking.

open as a page

What is a SupervisorJob in Kotlin coroutines, and how does it differ from a regular Job when one child fails?

level: juniorimportance: must knowfreq 70%
basics
~10 s

A SupervisorJob is a special parent for coroutines. If one child crashes, the others keep running and the parent stays alive. With a regular Job, one crash cancels everything.

open as a page

You launch a coroutine that runs a tight CPU-bound while-loop, then call cancel() on its Job. The loop keeps running. Why, and what is the simplest fix?

level: juniorimportance: must knowfreq 70%
basics
~10 s

Cancellation is cooperative: the coroutine only stops if it checks for cancellation. A pure CPU loop never checks, so it ignores cancel(). Add a check inside the loop, such as while (isActive).

open as a page

In Kotlin coroutines, what actually happens when you call Job.cancel()? Does the coroutine stop immediately?

level: juniorimportance: must knowfreq 75%
basics
~10 s

No. cancel() just marks the job as cancelled. The coroutine actually stops the next time it pauses at a suspending call. If it never pauses, it keeps running.

open as a page

Why can a suspending cleanup call inside a finally block fail to run after a coroutine is cancelled, and how does NonCancellable fix it?

level: juniorimportance: must knowfreq 55%
basics
~10 s

When a coroutine is cancelled, trying to suspend again (like awaiting a call) in finally immediately throws cancellation, so cleanup is skipped. Wrapping that cleanup in withContext(NonCancellable) lets it suspend and complete.

open as a page

What do withTimeout and withTimeoutOrNull do in Kotlin coroutines, and how do they differ?

level: juniorimportance: must knowfreq 70%
basics
~10 s

Both run a block of code with a time limit. If the work takes too long, withTimeout throws an error, while withTimeoutOrNull just returns null. Use them so slow operations don't hang forever.

open as a page

Compare isActive, ensureActive() and yield() for making a loop cancellable. When would you pick each?

level: middleimportance: must knowfreq 60%
basics
~20 s

isActive is a Boolean you test to exit the loop without throwing. ensureActive() throws CancellationException if cancelled. yield() throws too and also lets other coroutines run. Pick by whether you want to throw and/or reschedule.

open as a page

When you call async { ... } and the block throws an exception, at what point is that exception observed by the caller?

level: juniorimportance: must knowfreq 70%
basics
~10 s

The exception is stored inside the Deferred result and is re-thrown when you call await() on it. Calling async itself does not throw.

open as a page

What is CoroutineExceptionHandler in Kotlin coroutines, and what kind of failures does it handle?

level: juniorimportance: must knowfreq 70%
basics
~10 s

It is a piece of coroutine context that acts as a last-resort catcher. When a coroutine started with launch fails and nobody else handles the exception, this handler runs instead of crashing silently.

open as a page

In Kotlin coroutines, what happens when a coroutine started with `launch` throws an uncaught exception?

level: juniorimportance: must knowfreq 70%
basics
~10 s

The coroutine fails. The error travels up to its parent, which cancels itself and its other child coroutines, and then the error is reported.

open as a page

What is the difference between a regular Job and a SupervisorJob when one of their child coroutines fails?

level: juniorimportance: must knowfreq 70%
basics
~10 s

With a normal Job, if one child crashes, the parent and all the other children are cancelled too. With a SupervisorJob, a crashing child only affects itself; its siblings keep running.

open as a page

Explain why a failing root async can cancel its entire scope even though its exception is only thrown at await(). How do deferral and structured concurrency interact here?

level: middleimportance: must knowfreq 60%
basics
~20 s

await() decides where YOU see the error. But the async is also a child coroutine, so when it fails it tells its parent Job, which cancels the parent and all siblings — regardless of whether you await.

open as a page

What is a Channel<T> in Kotlin coroutines, and how do producer and consumer coroutines communicate through it?

level: juniorimportance: must knowfreq 70%
basics
~10 s

A Channel is a pipe between coroutines. One coroutine puts values in with send, another takes them out with receive. Both suspend (wait politely) until the other side is ready.

open as a page

Why shouldn't you use the JVM `synchronized` block (or a `ReentrantLock`) to protect shared state inside a coroutine, and what does Kotlin offer instead?

level: juniorimportance: must knowfreq 70%
basics
~10 s

synchronized blocks the whole thread while it waits. Coroutines share threads, so blocking one stalls others. Kotlin's Mutex instead suspends the coroutine, freeing the thread for other work.

open as a page

Explain the Channel capacity options RENDEZVOUS, BUFFERED, CONFLATED, and UNLIMITED, and how each affects send.

level: middleimportance: must knowfreq 60%
basics
~10 s

Capacity controls how many values a channel holds before send has to wait. Rendezvous holds none, buffered holds a few, unlimited holds everything, and conflated keeps only the latest and never waits.

open as a page

Explain what `Mutex.withLock { }` does mechanically, including how it behaves on exceptions and cancellation. How does it differ from manual `lock()`/`unlock()`?

level: middleimportance: must knowfreq 60%
basics
~10 s

withLock acquires the lock, runs your block, and always releases it afterward — even if the block throws or the coroutine is cancelled. Manual lock()/unlock() makes you remember to release it yourself.

open as a page

Why does `produce` return a `ReceiveChannel` while `actor` returns a `SendChannel`, and what are the receiver scopes inside each?

level: middleimportance: must knowfreq 35%
basics
~20 s

With produce the coroutine makes values for you, so you get the read end (ReceiveChannel). With actor you feed it messages, so you get the write end (SendChannel). Each is the opposite end of the channel from what the coroutine uses.

open as a page