What you'll learn
Welcome to Phase 6. Coroutines are Kotlin's answer to asynchronous programming — a way to write non-blocking, concurrent code that reads like ordinary sequential code. They power modern Android apps and high-throughput servers, and they're one of the best reasons to choose Kotlin.
By the end you'll be able to:
- Explain the difference between blocking and suspending
- Write and call
suspendfunctions - Start coroutines with
launch,async, andrunBlocking - Run work concurrently and collect results with
await
Blocking vs suspending
A traditional call like Thread.sleep(1000) blocks — it holds the thread hostage doing nothing for a full second. Threads are expensive, so blocking thousands of them doesn't scale. A coroutine delay(1000) instead suspends: it releases the thread so other work can run, then resumes later. The code still reads top-to-bottom, but nothing is wasted.
import kotlinx.coroutines.*
fun main() = runBlocking { // bridge into the coroutine world
println("Start")
launch { // start a coroutine, don't wait for it
delay(1000L) // suspend 1s WITHOUT blocking the thread
println("World!")
}
println("Hello,") // runs while the coroutine is delayed
}Key idea
kotlinx.coroutines library, added via Gradle.suspend functions
The suspend modifier marks a function that can pause and resume. Inside it you can call other suspend functions (like delay) as if they were normal — the compiler handles the pausing for you. The catch: a suspend function can only be called from a coroutine or another suspend function, which is how Kotlin keeps async code honest.
import kotlinx.coroutines.*
// A suspend function can pause and resume. It can only be called from a
// coroutine or from another suspend function — never from ordinary code.
suspend fun fetchUser(): String {
delay(500L) // pretend this is a network request
return "Ada"
}
fun main() = runBlocking {
println("Fetching...")
val user = fetchUser() // suspends here; the thread stays free
println("Got $user")
}Note
suspend doesn't make it run on a background thread by itself — it simply grants it the ability to suspend. Where it runs is decided by the dispatcher, which you'll meet in the next module.launch, async & runBlocking
Coroutines are started by builders. runBlocking bridges the normal world and the coroutine world (great for main and tests). launch fires off a coroutine that returns no result — "fire and forget." async starts one thatdoes return a result. Starting two async blocks lets their work overlap:
import kotlinx.coroutines.*
import kotlin.system.measureTimeMillis
suspend fun loadA(): Int { delay(1000L); return 10 }
suspend fun loadB(): Int { delay(1000L); return 20 }
fun main() = runBlocking {
val time = measureTimeMillis {
val a = async { loadA() } // starts right away
val b = async { loadB() } // starts right away — runs concurrently
println("Sum: ${a.await() + b.await()}") // wait for both results
}
println("Took ~${time}ms") // ~1000ms, not 2000ms
}Tip
launch when you don't need a result and asyncwhen you do.Job, Deferred & await
Every builder returns a handle. launch returns a Job you can join() (wait for) or cancel(). async returns a Deferred<T> — a Job that also carries a future result you retrieve with await(). These handles are how you control a coroutine's lifecycle:
import kotlinx.coroutines.*
fun main() = runBlocking {
val job = launch { // launch returns a Job handle
repeat(5) { i ->
delay(200L)
println("working $i")
}
}
delay(500L)
println("cancelling")
job.cancel() // stop the coroutine
job.join() // wait until it has finished stopping
println("done")
}Recap & quick check
Key takeaways
- Blocking holds a thread idle; suspending frees the thread to do other work and resumes later.
- A coroutine is a lightweight, suspendable computation — you can run huge numbers of them cheaply.
- suspend functions can pause and resume; they may only be called from a coroutine or another suspend function.
- runBlocking bridges into coroutines; launch is fire-and-forget; async returns a result.
- launch returns a Job (join/cancel); async returns a Deferred whose value you get with await().
Quick check
1. What is the key difference between delay() and Thread.sleep()?
2. Where can a suspend function be called from?
3. Which builder should you use to get a result back concurrently?
4. What does launch return?
5. Why do two 1-second async tasks finish in ~1 second?
You can now write asynchronous code that reads sequentially. Next we'll control where coroutines run — threads, thread pools, and the main thread — with coroutine context and dispatchers.