Kotlin Coroutines Basics
A coroutine is a piece of code that can pause its execution without blocking the thread it runs on, and resume later from where it left off. Coroutines make it easy to write code that fetches data from the internet, reads files, or waits for something — all without freezing your app.
The Problem — Why Blocking is Bad
Imagine you have one worker (one thread) at a restaurant. Blocking approach: Worker takes order → WAITS at the kitchen (thread blocked) → Kitchen finishes → Serves food → Takes next order The counter is empty while the worker waits. Coroutines approach: Worker takes order → Goes to next customer while food is prepared → Kitchen signals food is ready → Worker returns and serves food The counter never stops moving.
On Android, the main thread runs the UI. If you block it with a network call, the app freezes. Coroutines keep it free.
Adding Coroutines to Your Project
// In build.gradle.kts (app level)
dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3")
}
Your First Coroutine
import kotlinx.coroutines.*
fun main() = runBlocking {
println("Start")
launch {
delay(1000L) // pause 1 second (non-blocking)
println("Coroutine done")
}
println("After launch")
}
// Output:
// Start
// After launch
// Coroutine done ← arrives 1 second later
Diagram — Coroutine Execution Flow
Time → 0ms 1ms 1000ms
│ │ │
main: Start ──► After ──► (waiting)
launch
│
launch: (started)──────────────► Coroutine done
paused with delay(1000)
Key Coroutine Concepts
suspend Functions
A suspend function can pause without blocking. It can only be called from another suspend function or a coroutine.
suspend fun fetchData(): String {
delay(2000L) // simulate network delay
return "User data loaded"
}
fun main() = runBlocking {
println("Fetching...")
val data = fetchData() // pauses here, does not block thread
println(data)
}
// Fetching...
// User data loaded (2 seconds later)
Coroutine Builders
Builder | Returns | Behavior ---------------|----------|------------------------------------------ runBlocking | T | Blocks the thread — for main() and tests only launch | Job | Fire-and-forget; does not return a value async | Deferred | Returns a value; use await() to get it
launch — Fire and Forget
fun main() = runBlocking {
val job = launch {
delay(500)
println("Background task complete")
}
println("Main continues")
job.join() // wait for the job to finish
println("Done")
}
// Main continues
// Background task complete
// Done
async / await — Concurrent with a Result
fun main() = runBlocking {
val result1 = async { fetchScore("Alice") }
val result2 = async { fetchScore("Bob") }
val total = result1.await() + result2.await()
println("Total: $total")
}
suspend fun fetchScore(name: String): Int {
delay(1000L)
return if (name == "Alice") 90 else 85
}
// Both fetches run concurrently — total wait ~1 second, not 2
Coroutine Scope
Every coroutine runs in a scope. The scope controls the coroutine's lifetime. When the scope is cancelled, all coroutines inside it are cancelled too.
Scope | Where to use --------------------|-------------------------------------------- runBlocking | Tests and main() only GlobalScope | App-wide (avoid — no lifecycle awareness) viewModelScope | Android ViewModel (cancelled with ViewModel) lifecycleScope | Android Activity/Fragment CoroutineScope | Custom scope you manage
Coroutine Dispatchers
A dispatcher decides which thread (or thread pool) a coroutine runs on.
Dispatcher | Runs on -----------------------|------------------------------------------- Dispatchers.Main | Main (UI) thread — for UI updates Dispatchers.IO | Thread pool for file/network operations Dispatchers.Default | CPU-intensive work (sorting, parsing) Dispatchers.Unconfined | Current thread (rarely used)
import kotlinx.coroutines.*
fun main() = runBlocking {
launch(Dispatchers.IO) {
println("Running on IO: ${Thread.currentThread().name}")
val data = simulateFetch()
withContext(Dispatchers.Main) {
println("Update UI: $data")
}
}
}
suspend fun simulateFetch(): String {
delay(500)
return "Server response"
}
Diagram — Switching Dispatchers
[Dispatchers.IO] [Dispatchers.Main]
Network call (background) → Update UI (main thread)
simulateFetch() → withContext(Main) { ... }
Thread blocked doing work? No. coroutine suspends, thread is free.
