skip to content

coroutineScope vs supervisorScope

coroutineScope and supervisorScope both suspend until every child completes; they differ in whether one child's failure cancels its siblings. Picking between them is how you decide whether a group of tasks succeeds or fails together.

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

questions

5

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

level: juniorimportance: must knowfreq 70%

answer

  1. suspend, not block
  2. waits for ALL children
  3. new Job, inherited context
  4. returns block result
  5. structured concurrency boundary

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.

solid answer

~40 s

coroutineScope is a suspend function that creates a new scope, runs the given block, and suspends until every child coroutine launched inside it completes. Unlike launch (which is fire-and-forget and returns a Job immediately) or async (which returns a Deferred), coroutineScope returns the block's own result and only resumes the caller once all children are done. Unlike runBlocking, it does not block the calling thread — it suspends, so it must be called from another suspend function or coroutine. It inherits the parent's CoroutineContext (dispatcher, etc.) but installs a fresh Job, making it a structured-concurrency boundary: if any child fails, the scope cancels the rest and rethrows.

code

kotlin · 8 lines
kotlin
suspend fun main() {
    val result = coroutineScope {
        val a = async { 1 }
        val b = async { 2 }
        a.await() + b.await()
    }
    println(result) // 3, printed only after both finish
}

go deeper

for a junior

Knows it runs a block, waits for all children, and suspends rather than blocks.

for a middle

Contrasts it cleanly with launch/async/runBlocking/withContext and mentions inherited context plus new Job.

for a senior

Frames it as a structured-concurrency boundary and explains failure propagation (one failure cancels siblings).

for a principal

Discusses where to place such boundaries in an API for cancellation safety and how it composes with supervisorScope and custom CoroutineScopes.

## What coroutineScope is `coroutineScope { ... }` is a **suspending function** (you can only call it from a coroutine or another `suspend` function). It creates a new `CoroutineScope`, runs the lambda you pass, and **suspends the caller until all coroutines started inside the block have completed**. Its return value is whatever the block returns. ## Structured concurrency boundary The key idea is *structured concurrency*: child coroutines have a defined lifetime tied to their scope. `coroutineScope`: - **Inherits** the parent `CoroutineContext` (dispatcher, `CoroutineName`, etc.). - Installs a **new `Job`** as the parent of all children launched inside it. - Does **not return** until every child finishes (success, failure, or cancellation). ```kotlin suspend fun loadDashboard(): Dashboard = coroutineScope { val user = async { fetchUser() } // child 1 val stats = async { fetchStats() } // child 2 Dashboard(user.await(), stats.await()) // block result } ``` `loadDashboard` only resumes after both `async` children complete. ## How it differs from the builders - **`launch`**: a coroutine builder that returns a `Job` immediately and runs fire-and-forget. It does *not* wait. `coroutineScope` is not a builder you forget — it *awaits* its children. - **`async`**: returns a `Deferred<T>`; you must `await()` it. `coroutineScope` returns the plain block result directly. - **`runBlocking`**: **blocks** the current thread until completion and is meant as a bridge from non-coroutine code (e.g. `main`, tests). `coroutineScope` **suspends** instead of blocking, so it never ties up a thread. - **`withContext`**: switches the `CoroutineContext` (e.g. dispatcher) for the block; `coroutineScope` keeps the same context but adds a Job boundary. ## Failure semantics With `coroutineScope`, if **any** child fails, the scope cancels the remaining children and rethrows the exception from `coroutineScope` itself. This is the contrast with `supervisorScope`, where children fail independently.

  • Can you call coroutineScope from a regular (non-suspend) function?
    No. It is a suspend function, so it must be called from a coroutine or another suspend function. From plain code you'd use runBlocking or a CoroutineScope.launch instead.
  • Does coroutineScope create a new thread?
    No. It inherits the parent's dispatcher and runs on whatever thread(s) that dispatcher provides; it only adds a Job, not a thread.

Like a foreman who hands out tasks and won't leave the site until every worker has finished.

saying these in an interview costs you the question

  • Saying it blocks the thread like runBlocking
  • Thinking it returns immediately like launch
  • Claiming it creates a new dispatcher or thread
  • Not knowing it waits for all children

context

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

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

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 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