Skip to content

Beginner: Kotlin · Coroutines and Flow · topic 15 of 50 · 30 min

launch, async and structured concurrency

Explanation

Coroutines start inside a CoroutineScope. launch starts a coroutine that does a job and returns a Job; async starts one that produces a value and returns a Deferred, whose await() gives the result. Structured concurrency means every coroutine belongs to a scope: the parent waits for its children, and cancelling the scope cancels them all, so nothing leaks. On Android you use viewModelScope and lifecycleScope.

Scopes: where coroutines live

A coroutine is always started from a CoroutineScope, an object that tracks the coroutines started in it. When the scope ends or is cancelled, all its coroutines are cancelled too. This rule, called structured concurrency, prevents work from running forever in the background after a screen is closed, which wastes battery and data and can crash the app.

ScopeWhereCancelled when
viewModelScopeInside a ViewModelThe ViewModel is cleared (the screen is gone for good)
lifecycleScopeInside an Activity or FragmentThe lifecycle is destroyed
coroutineScope { }Inside a suspend functionIt finishes when all its children finish
runBlockingmain and tests onlyIts block finishes (it blocks the thread)
Think of it like this: A scope is like a school trip with a teacher in charge. Every child (coroutine) is on the teacher's list. When the trip ends, the teacher makes sure every child is back on the bus; nobody is left wandering the city. If the trip is cancelled, every child is called back.

launch: fire and forget

kotlin
import kotlinx.coroutines.*

fun main() = runBlocking {
    val job: Job = launch {
        delay(500)
        println("Receipt emailed")
    }
    println("Payment done")
    job.join()            // wait for the job to finish (optional)
}
Prints Payment done, then Receipt emailed. launch returns a Job you can join or cancel.

async: get a result

kotlin
suspend fun price(): Int { delay(1000); return 499 }
suspend fun stock(): Int { delay(1000); return 12 }

fun main() = runBlocking {
    val p = async { price() }      // both start now
    val s = async { stock() }
    println("Rs ${p.await()}, ${s.await()} left")   // about 1 second in total, not 2
}
async returns a Deferred<T>. await() suspends until the value is ready. Two independent calls run concurrently.

Use launch when you do not need a result (save, send, log). Use async when you need a value, and especially to run several independent calls at the same time.

Structured concurrency in practice

kotlin
suspend fun loadDashboard(): String = coroutineScope {
    val user = async { delay(300); "Asha" }
    val orders = async { delay(500); 7 }
    "${user.await()} has ${orders.await()} orders"
}   // returns only when both children are done; if one fails, the other is cancelled
coroutineScope groups child coroutines inside a suspend function.

Cancellation

Cancelling a scope or Job asks its coroutines to stop. Cancellation is cooperative: a coroutine notices it at suspension points like delay, or when it checks isActive. Long CPU loops should call ensureActive() or yield() now and then. Library suspend functions (Retrofit, Room) already cooperate.

kotlin
fun main() = runBlocking {
    val job = launch {
        repeat(100) { i ->
            println("Uploading part $i")
            delay(100)
        }
    }
    delay(350)
    job.cancel()           // user pressed Cancel
    println("Upload cancelled")
}
Prints parts 0 to 3, then Upload cancelled.

Errors

An exception inside launch cancels the parent scope by default and is reported as an uncaught error. Inside async, the exception is thrown when you call await(). The simplest rule: wrap the risky call in try/catch inside the coroutine and turn failures into an error state for the UI.

kotlin
viewModelScope.launch {
    try {
        val data = repository.load()
        _state.value = UiState.Success(data)
    } catch (e: IOException) {
        _state.value = UiState.Error("No internet connection")
    }
}
A typical Android ViewModel pattern (module 7). Do not catch CancellationException and ignore it.

Common mistakes

  • Using GlobalScope: its coroutines are not tied to any screen and can leak. Use viewModelScope or lifecycleScope.
  • Awaiting async calls one by one right after starting each (async { a() }.await()), which removes the speed-up. Start all, then await.
  • Catching Exception broadly and swallowing CancellationException, which breaks cancellation.
  • Using runBlocking in Android UI code: it freezes the main thread.

Need help?

Stuck on a word or an idea? Search this course, or every course, and jump straight to the explanation or a worked question.

Try:

Watch the lesson

launch, async and structured concurrency · 1/7

Scope owns its coroutines

Every coroutine is started inside a scope. When the scope ends, its coroutines are cancelled. This is structured concurrency.

Prefer a video by a teacher? Find free videos on “kotlin coroutines launch async await structured concurrency viewModelScope” on YouTube ↗

In real life: A home screen that loads three things

A shopping app's home screen needs the user profile, today's offers and recently viewed items, each from a different API taking about one second. Loading them one after another takes three seconds. The ViewModel starts three async calls inside viewModelScope and awaits them together, so the screen fills in about one second. If the user leaves the screen, viewModelScope cancels all three requests automatically.

kotlin
import kotlinx.coroutines.*

suspend fun profile(): String { delay(1000); return "Rohit" }
suspend fun offers(): Int { delay(1000); return 5 }
suspend fun recent(): Int { delay(1000); return 3 }

fun main() = runBlocking {
    val start = System.currentTimeMillis()
    val a = async { profile() }
    val b = async { offers() }
    val c = async { recent() }
    println("${a.await()}: ${b.await()} offers, ${c.await()} recent")
    println("took about ${(System.currentTimeMillis() - start) / 1000} s")   // about 1 s
}
Three concurrent calls finish in roughly the time of one.

Practice (0/3 done)

Task 1 · Question

Two independent suspend calls each take 1 second. With async for both, then await for both, about how long does it take?

Task 2 · Question

Which scope should a ViewModel use to start coroutines?

Task 3 · Practice

Write suspend fun downloadAll(urls: List<String>): List<Int> that uses coroutineScope and async to "download" every URL at once (delay a random 200-800 ms and return the URL length). In main, call it with 5 URLs and print the results and total time. Then add a launch that cancels a long repeat loop after 1 second.

Quick quiz

Question 1

1.What does launch return?

Question 2

2.What does async return?

Question 3

3.Why avoid GlobalScope in apps?

Question 4

4.Where does an exception inside async surface?

Read more (free)

Open a question to see the answer and why. The first ones are this topic's own quiz, so try the quiz above before you peek.

What does launch return?

Answer: A Job

A Job lets you join or cancel the coroutine.

What does async return?

Answer: A Deferred<T> whose await() gives the value

Deferred is a Job with a result.

Why avoid GlobalScope in apps?

Answer: Its coroutines are not tied to a screen and can leak

Structured scopes cancel work automatically.

Where does an exception inside async surface?

Answer: When you call await()

await rethrows the exception.

Which is the best description of a coroutine?Related

Answer: A lightweight task that can suspend without blocking a thread

Coroutines run on threads but are much lighter.

From Interview questions: Kotlin

What kind of work benefits most from async/await?Related

Answer: Waiting for network, disk or database (I/O)

async frees threads during I/O waits.

From async and await: waiting without blocking

async function f() { return 3; } What is f()?Related

Answer: A promise that fulfils with 3

async functions always return promises.

From async and await

Which is the correct way to wait inside a coroutine?Related

Answer: delay(1000)

delay suspends only the coroutine.

From Why coroutines? suspend functions

Pass the quiz (60% or more) or mark this topic as done when you are ready.

Your progress is saved on this device only.

Next steps

  1. 2 more topics in “Coroutines and Flow” before its exam: Dispatchers and withContext, Flow and StateFlow.
  2. Module 4 exam: Coroutines and Flow · 13 questions · 25 min, without help. 4 topics of this module still to finish first.
  3. Then the next module, Android Studio and your first app. 50 topics left in the course before the final exam.

← Previous: Why coroutines? suspend functions