Coroutines are Kotlin's standard for concurrency on Android and the server, replacing callbacks and blocking threads.
A coroutine is a lightweight, suspendable computation. Thousands can run on a few threads because, while one waits for I/O or a delay, it suspends and frees its thread instead of blocking it.
The language provides suspend functions; the library kotlinx.coroutines (add org.jetbrains.kotlinx:kotlinx-coroutines-core) provides builders and utilities.
- A
suspend funcan call other suspending functions such asdelay(100). It can only be called from another suspend function or a coroutine. runBlocking { }starts a coroutine and blocks the current thread until it finishes: formainfunctions and tests only.launch { }starts a coroutine for side effects and returns aJob(join()waits for it).async { }starts a coroutine that computes a value and returns aDeferred<T>;await()suspends until the result is ready.coroutineScope { }groups child coroutines in a suspend function and waits for all of them.
Code inside a coroutine reads sequentially: val user = fetchUser(); val orders = fetchOrders(user). To run independent calls concurrently, start both with async and then await both.
Use delay instead of Thread.sleep inside coroutines: sleep blocks the thread. For blocking APIs (JDBC, file I/O), wrap them in withContext(Dispatchers.IO) { } (next lesson).
Frameworks provide scopes for you: viewModelScope and lifecycleScope on Android, suspend handlers in Ktor and Spring WebFlux.
Example
// build.gradle.kts: implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.2")
import kotlinx.coroutines.*
import kotlin.system.measureTimeMillis
suspend fun fetchUser(): String { delay(300); return "Ada" } // simulated I/O
suspend fun fetchScore(user: String): Int { delay(300); return user.length * 10 }
fun main() = runBlocking {
val seq = measureTimeMillis {
val user = fetchUser()
println("$user ${fetchScore(user)}") // Ada 30
}
println("sequential ~${seq / 100 * 100} ms") // sequential ~600 ms
val par = measureTimeMillis {
val a = async { fetchScore("Ada") } // both start now
val b = async { fetchScore("Grace") }
println(a.await() + b.await()) // 80
}
println("concurrent ~${par / 100 * 100} ms") // concurrent ~300 ms
val job = launch { repeat(3) { i -> println("tick $i"); delay(50) } }
job.join() // tick 0 / tick 1 / tick 2
val jobs = List(10_000) { launch { delay(100) } } // cheap: 10k coroutines
jobs.joinAll()
println("done")
}Remember
- suspend functions pause without blocking their thread
- launch for fire-and-forget Jobs, async/await for results
- Start several async calls before awaiting to run them concurrently
- runBlocking only in main and tests; use delay, not Thread.sleep
Watch out for
- Calling Thread.sleep or blocking I/O inside a coroutine on a limited dispatcher, starving other coroutines.