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