Coroutines Workbook


Practice problems for Coroutines: Asynchronous Code That Reads Like It Isn’t. Each takes a minute or two. Write your own answer first, then click Show answer — nothing here is a trick question, just direct practice of the ideas from the lesson.

suspend functions

1. Mark a function suspendable

You have fun loadUser(id: Int): User that calls another suspending function. What one keyword does it need?

Show answer Hide answer
suspend fun loadUser(id: Int): User { /* ... */ }

suspend — it can pause at a suspension point and resume later.

2. Sequential suspends

Write a suspend fun loadOrders(id) that calls suspend fun fetchUser(id) then suspend fun fetchOrders(user) in order.

Show answer Hide answer
suspend fun loadOrders(id: Int): List<Order> {
    val user = fetchUser(id)
    return fetchOrders(user)
}

It reads like blocking code, but each call is a suspension point that may release the thread.

3. Call a suspend from a suspend

Write a suspend fun outer() that calls a suspend fun inner() — one of the two places a suspend function may legally be called.

Show answer Hide answer
suspend fun inner() { /* ... */ }

suspend fun outer() {
    inner()   // allowed: a suspend function may call another
}

(The other place is inside a coroutine started by launch or async.)

builders

4. Fire and forget

Inside a scope, start a coroutine that loads orders and displays them, without returning a result.

Show answer Hide answer
scope.launch {
    val orders = loadOrders(42)
    display(orders)
}

5. Run two things concurrently

Inside a coroutineScope, fetch a user and their settings concurrently, then render both.

Show answer Hide answer
coroutineScope {
    val user = async { fetchUser(id) }
    val settings = async { fetchSettings(id) }
    render(user.await(), settings.await())
}

The two async blocks overlap; await() suspends until each result is ready.

6. Hold a Deferred, then await it

Inside a coroutine, start fetchUser(id) with async, store the handle, then later get the value out of it.

Show answer Hide answer
val deferred = async { fetchUser(id) }
// ... other work ...
val user = deferred.await()

async returns a Deferred<T>; await() suspends until the value is ready.

structure

7. Children finish before the scope

Inside a coroutineScope, launch two independent jobs, saveA() and saveB(). The block must return only after both finish.

Show answer Hide answer
coroutineScope {
    launch { saveA() }
    launch { saveB() }
}
// returns only once both launched children complete

That guarantee is structured concurrency — and if one child fails, the others are cancelled.

8. Run blocking I/O off the main thread

Start a coroutine that performs blocking readFile() on the dispatcher meant for I/O.

Show answer Hide answer
scope.launch(Dispatchers.IO) {
    val text = readFile()
}

Dispatchers.IO is for blocking I/O; Dispatchers.Default is for CPU-bound work.

9. Fetch two things, return both

Write a suspend fun loadDashboard(id: Int): Dashboard that fetches fetchUser(id) and fetchSettings(id) concurrently and returns a Dashboard(user, settings).

Show answer Hide answer
suspend fun loadDashboard(id: Int): Dashboard = coroutineScope {
    val user = async { fetchUser(id) }
    val settings = async { fetchSettings(id) }
    Dashboard(user.await(), settings.await())
}

Sequential-looking code, but the two fetches overlap.


That’s the last workbook in the series. Head back to the lesson, Coroutines, or return to where it all started: In Kotlin, There Are No Primitives.