skip to content

Coroutine Builders

The functions that actually start coroutines — launch, async, runBlocking — plus the scoping builders that suspend until their children finish. Choosing the right builder is the first design decision in any coroutine code.

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

explore

questions

25

What does the async coroutine builder return, and how do you get the result out of it?

level: juniorimportance: must knowfreq 80%

answer

  1. async returns Deferred<T>
  2. await() suspends to get value
  3. launch=Job, async=Deferred
  4. starts eagerly by default
  5. await rethrows the exception

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.

solid answer

~40 s

async is a coroutine builder on CoroutineScope that starts a new coroutine and immediately returns a Deferred<T> — a future-like handle. The coroutine runs concurrently; you don't get the value back from async itself. To obtain the computed value you call the suspending function await() on the Deferred, which suspends the caller until the result is ready and then returns it (or rethrows any exception the coroutine threw). Use async when you want a value back; use launch when you only need a side effect (it returns Job, not Deferred). Both start eagerly by default. await() does not block a thread — it suspends, so other work can proceed on the same thread.

code

kotlin · 4 lines
kotlin
suspend fun price(): Int = coroutineScope {
    val d: Deferred<Int> = async { 21 * 2 }
    d.await() // 42
}

go deeper

for a junior

Knows async returns Deferred and await() retrieves the value; distinguishes it from launch.

for a middle

Explains suspension vs blocking and eager start, and that await rethrows exceptions.

for a senior

Frames async as scope-bound structured concurrency and notes CoroutineStart.LAZY and Deferred-is-a-Job.

for a principal

Discusses when returning Deferred across API boundaries is appropriate vs keeping it internal to a scope.

## What `async` is `async` is a **coroutine builder**: a function that starts a new coroutine. It is an extension on `CoroutineScope`. Unlike `launch` (which returns a `Job` and is used for fire-and-forget side effects), `async` returns a **`Deferred<T>`**. ## `Deferred<T>` `Deferred<T>` is a `Job` that also carries a **future result** of type `T`. Think of it as a promise/future: a handle to a value that will exist later. Because `Deferred` *is a* `Job`, it also has lifecycle methods like `cancel()` and `isActive`. ## `await()` To read the value you call `await()`, a **suspending function**. "Suspending" means it can pause the current coroutine without blocking the underlying thread, freeing that thread for other work, and resume later when the result is ready. - If the coroutine already finished, `await()` returns immediately. - If it is still running, `await()` **suspends** until completion. - If the coroutine threw, `await()` **rethrows** that exception. ```kotlin suspend fun loadUser(): User = coroutineScope { val deferred: Deferred<User> = async { fetchUserFromDb() } // starts now // ... can do other work here while it runs ... deferred.await() // suspend until the value is ready, then return it } ``` ## Eager start By default `async` uses `CoroutineStart.DEFAULT`, so the coroutine begins **eagerly** as soon as `async` is called — not lazily when `await()` is invoked. (You can pass `start = CoroutineStart.LAZY` to defer it until `await()` or `start()`.) ## Key keywords/APIs - `async { ... }` — builder returning `Deferred<T>` - `Deferred<T>` — future-like Job carrying a result - `await()` — suspends to obtain the value or rethrow - contrast `launch` → `Job` (no result)

  • What does launch return instead, and when would you use it?
    launch returns a Job (no result value). Use it for fire-and-forget side effects where you don't need a return value.
  • Does calling await() block the thread?
    No. await() is a suspending function: it suspends the coroutine and frees the thread; it doesn't block it.

async is a coat-check ticket: you hand off work, get a ticket (Deferred), and redeem it later with await().

saying these in an interview costs you the question

  • Saying async returns the value directly instead of a Deferred
  • Claiming await() blocks the calling thread
  • Confusing async (Deferred) with launch (Job)
  • Thinking async only starts when await() is called (it's eager by default)

context

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

How do you run two independent suspending calls concurrently and combine their results, and how is that different from calling them one after another?

level: middleimportance: must knowfreq 75%

basics

~10 s

Start each call with async so they run at the same time, then await both. Calling them sequentially (just suspend calls) waits for the first to finish before starting the second, so it's slower.

open as a page

What does job.join() do on a Job returned by launch, and how does it differ from awaiting a result?

level: middleimportance: must knowfreq 70%

basics

~20 s

join() is a suspending call that waits until the launched coroutine finishes. It pauses the caller until the job completes but gives back no value — unlike await(), which both waits and returns a result.

open as a page

How does runBlocking differ from launch and async as a coroutine builder?

level: middleimportance: must knowfreq 70%

basics

~10 s

launch and async start coroutines that run alongside your code and return right away. runBlocking is a regular function that waits, blocking the thread until everything finishes, and gives back the result.

open as a page

Explain the failure-propagation difference between coroutineScope and supervisorScope when a child coroutine throws.

level: middleimportance: must knowfreq 75%

basics

~10 s

In coroutineScope, if one child fails, the others get cancelled and the failure spreads up. In supervisorScope, one child failing does not cancel its siblings; each child fails on its own.

open as a page

When would you choose withContext over async { }.await() for running work on another dispatcher?

level: middleimportance: must knowfreq 65%

basics

~10 s

Use withContext when you just need to run one block somewhere else and get its result in sequence. Use async/await only when you want two or more things running at the same time.

open as a page

How are exceptions handled when an async coroutine throws? When is the exception delivered, and how does the enclosing scope affect propagation?

level: seniorimportance: must knowfreq 60%

basics

~20 s

An exception from async is stored in its Deferred and rethrown when you call await(). But under a normal coroutineScope it also cancels the parent and siblings immediately, so the failure surfaces even before you await.

open as a page

How does a failure inside a launch block propagate, and what effect does it have on the parent and sibling coroutines?

level: seniorimportance: must knowfreq 60%

basics

~20 s

If a launch block throws an uncaught exception, the failure travels up to its parent. With a normal parent, this cancels the parent and all its other children. The exception is delivered eagerly when it happens.

open as a page

When you fan out into a list of Deferreds, what does awaitAll do, and how does it behave on failure compared to awaiting in a loop?

level: middleimportance: should knowfreq 55%

basics

~10 s

awaitAll waits for every Deferred in a list and returns all their results as a list. If any one fails, awaitAll fails fast immediately instead of waiting for the slower remaining ones.

open as a page

What does the start parameter of launch control, and how does CoroutineStart.LAZY differ from CoroutineStart.DEFAULT?

level: middleimportance: should knowfreq 55%

basics

~10 s

The start parameter decides when the coroutine actually begins running. DEFAULT starts it right away; LAZY creates the Job but waits to run until you trigger it with start() or join().

open as a page

Explain precisely what 'blocking the current thread' means for runBlocking, including its event loop and child coroutines.

level: middleimportance: should knowfreq 50%

basics

~10 s

The thread that calls runBlocking stays busy and cannot do other work until the coroutine finishes. Inside, runBlocking runs an event loop on that thread that drives the coroutines until they all complete.

open as a page

What CoroutineContext does coroutineScope/supervisorScope use, and how does the Job they install affect cancellation of children?

level: middleimportance: should knowfreq 55%

basics

~10 s

Both reuse the surrounding context (like the dispatcher) but add their own parent Job. Because that Job is the parent of every child, cancelling the scope cancels all the children inside it.

open as a page

Why is Dispatchers.IO the standard target for withContext when calling blocking APIs, and when would Dispatchers.Default be wrong or right?

level: middleimportance: should knowfreq 55%

basics

~20 s

Dispatchers.IO has a large pool meant for tasks that wait on the network or disk, so many can block at once. Dispatchers.Default has only as many threads as CPU cores, good for heavy calculations but easily clogged by blocking calls.

open as a page

When should you choose launch over async, and what problems arise from misusing launch for fire-and-forget work?

level: seniorimportance: should knowfreq 45%

basics

~20 s

Use launch when you start work but don't need its result; use async when you need a value back. Misusing launch — like running it on GlobalScope — can leak coroutines, hide errors, or outlive the component that started it.

open as a page

What are the dangers of calling runBlocking from inside a coroutine or suspend function, and how do you fix such code?

level: seniorimportance: should knowfreq 45%

basics

~20 s

It blocks a real thread that the coroutine system needs. On a small thread pool this wastes or even starves threads and can deadlock. Replace it with withContext, coroutineScope, or async/await — none of which block a thread.

open as a page

How is runBlocking used in tests, and why is runTest usually preferred?

level: seniorimportance: should knowfreq 55%

basics

~10 s

runBlocking lets a normal test method call suspend functions by waiting for them. But it really sleeps through delays, making tests slow. runTest fast-forwards virtual time, so tests run instantly with more control.

open as a page

You fan out N independent network calls and want partial results even if some fail. Which scope builder do you use, and how do you collect results safely?

level: seniorimportance: should knowfreq 50%

basics

~10 s

Use supervisorScope so one failing call doesn't cancel the others. Launch each call with async, then await each one inside its own try/catch so you can keep the successes and record the failures.

open as a page

How does withContext interact with cancellation and the parent CoroutineContext, including the Job and CoroutineExceptionHandler?

level: seniorimportance: should knowfreq 40%

basics

~20 s

withContext keeps you inside the same parent coroutine. You can override things like the dispatcher, but it still uses the parent's Job, so if the parent is cancelled the block is cancelled too, and errors propagate up normally.

open as a page

What does start = CoroutineStart.LAZY change about async, and what is a subtle pitfall of lazy Deferreds in parallel decomposition?

level: seniorimportance: nice to knowfreq 30%

basics

~20 s

LAZY means the async coroutine doesn't start until you trigger it (via await() or start()). The pitfall: if you rely on await() to start lazy tasks, they run one at a time instead of in parallel.

open as a page

How are exceptions ultimately delivered out of coroutineScope versus supervisorScope, including how CoroutineExceptionHandler and async interact with each?

level: seniorimportance: nice to knowfreq 35%

basics

~20 s

coroutineScope rethrows a child's failure to whoever called it. supervisorScope does not rethrow on its own; a failed launch child goes to the exception handler, and a failed async child shows up only when you await it.

open as a page

A coroutine is cancelled; you need a final cleanup suspend call to still run. How do you do that with withContext, and what are the pitfalls?

level: seniorimportance: nice to knowfreq 25%

basics

~10 s

Wrap the cleanup in withContext(NonCancellable) inside a finally block. That lets the suspending cleanup run even though the coroutine is already cancelled. Keep it short, because nothing can stop it.

open as a page