skip to content

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