skip to content

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

level: juniorimportance: must knowfreq 85%

answer

  1. launch = fire-and-forget, returns Job not a value
  2. Job = lifecycle handle: join / cancel / isActive
  3. Block returns Unit
  4. Extension on CoroutineScope
  5. Need a result? use async (Deferred)

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.

solid answer

~40 s

launch is a fire-and-forget coroutine builder: it starts a coroutine concurrently and returns a Job rather than a value. Because it returns Job (not Deferred), it is meant for work whose result you don't consume directly — side effects, logging, kicking off background tasks. The Job lets you observe and control the coroutine: call job.join() to suspend until it completes, job.cancel() to cancel it, or read job.isActive / isCompleted / isCancelled. launch must be called on a CoroutineScope, which supplies the CoroutineContext (dispatcher, Job, name) and defines the lifetime/structured-concurrency boundary. If you need a return value, you use async (which returns Deferred<T>) instead.

code

kotlin · 9 lines
kotlin
fun main() = runBlocking {
    val job: Job = launch {
        delay(50)
        println("background work finished")
    }
    println("isActive=${job.isActive}")
    job.join()
    println("isCompleted=${job.isCompleted}")
}

go deeper

for a junior

Knows launch starts background work and returns a Job; can name join/cancel.

for a middle

Contrasts launch (Job) vs async (Deferred), explains the scope requirement and dispatcher role.

for a senior

Frames launch within structured concurrency and lifetime ownership; discusses when fire-and-forget is appropriate vs awaiting a result.

for a principal

Reasons about API design intent (Unit block, Job vs Deferred), discourages GlobalScope, and ties launch choice to lifecycle/cancellation strategy across a codebase.

## What `launch` is `launch` is one of the core *coroutine builders* in `kotlinx.coroutines`. A **coroutine builder** is a function that takes a block of suspendable code and starts running it as a new coroutine. `launch` is the **fire-and-forget** builder: you start work and do not directly receive a result from it. Its simplified signature is: ```kotlin fun CoroutineScope.launch( context: CoroutineContext = EmptyCoroutineContext, start: CoroutineStart = CoroutineStart.DEFAULT, block: suspend CoroutineScope.() -> Unit ): Job ``` Key facts encoded here: - It is an **extension on `CoroutineScope`** — you must call it on a scope (e.g. `viewModelScope`, a scope from `coroutineScope { }`, or `GlobalScope`, which is discouraged). The scope supplies the **`CoroutineContext`** (dispatcher, parent `Job`, coroutine name) and defines the coroutine's lifetime. - The `block` returns **`Unit`** — `launch` produces no value. - It returns a **`Job`**. ## The `Job` it returns A **`Job`** is a cancellable handle to the coroutine's lifecycle — not its result. With it you can: - `job.join()` — a *suspending* call that waits until the coroutine completes (normally or with failure/cancellation). - `job.cancel()` — request cooperative cancellation. - inspect state via `job.isActive`, `job.isCompleted`, `job.isCancelled`. - attach `job.invokeOnCompletion { }` to run code when it finishes. Because it returns `Job` and not `Deferred<T>`, there is no `await()` — `await()` belongs to `async`. That distinction is the whole point: **`launch` = side effects, `async` = a value.** ## Concurrency, not parallelism guaranteed `launch` returns immediately; the coroutine runs concurrently with the rest of your code. Whether it runs in parallel depends on the **dispatcher** (e.g. `Dispatchers.Default` for CPU work, `Dispatchers.IO` for blocking I/O). ```kotlin fun main() = runBlocking { val job = launch { delay(100) println("done in background") } println("launched, job.isActive = ${job.isActive}") job.join() // wait for it } ``` ## When to use it Use `launch` when you want to start concurrent work and don't need its return value in the calling code: triggering a network write, updating UI state, emitting analytics, starting a long-running collector. If you need the produced value, reach for `async`/`await` instead.

  • How is launch different from async?
    launch returns a Job and is for side-effect/fire-and-forget work with no result; async returns a Deferred<T> whose value you obtain with await().
  • Can you call launch outside a CoroutineScope?
    No — it's an extension on CoroutineScope. You need a scope (e.g. runBlocking, coroutineScope, viewModelScope). GlobalScope works but is discouraged because it has no structured lifetime.

launch is like sending someone off to run an errand with a pager (the Job): you can page them to ask if they're done or call them back, but they don't hand you a receipt.

saying these in an interview costs you the question

  • Saying launch returns the result of the block
  • Confusing Job with Deferred or claiming you can call await() on a Job
  • Thinking launch blocks the caller until the coroutine finishes
  • Believing launch always runs in parallel regardless of dispatcher

context