Coroutines & Asynchronous Programming
The problem coroutines solve
Some tasks take time: downloading data, reading a file, querying a database. If you run them on the main thread, the app freezes. Older solutions (threads, callbacks) quickly become a tangled mess known as “callback hell”. Coroutines let you write asynchronous code that reads top-to-bottom like normal code, while running efficiently in the background.
suspend functions
A function marked suspend can pause without blocking the thread, and resume later. It can only be called from a coroutine or another suspend function.
suspend fun fetchUser(): User {
delay(1000) // pauses for 1s WITHOUT freezing the thread
return User(1, "Anand")
}
Launching a coroutine
import kotlinx.coroutines.*
fun main() = runBlocking {
launch {
val user = fetchUser() // waits, but doesn't block others
println(user.name)
}
println("This prints first")
}
launch— start a coroutine that returns no result (“fire and forget”).async— start one that returns a result youawait().
Running tasks in parallel
suspend fun loadDashboard() = coroutineScope {
val user = async { fetchUser() } // both start together
val posts = async { fetchPosts() }
Pair(user.await(), posts.await()) // wait for both
}
Done sequentially this would take 2 seconds; in parallel it takes about 1.
Dispatchers: choosing the thread
withContext(Dispatchers.IO) {
// network / database / files
}
// Dispatchers.Main -> UI work
// Dispatchers.Default -> heavy CPU work
Structured concurrency
Coroutines are organised in a tree via a scope. Cancel or fail the scope, and all its children stop too — no leaks. In Android, viewModelScope and lifecycleScope give you ready-made scopes tied to a screen’s lifetime.
Common mistakes
- Calling a
suspendfunction from normal code — it must be inside a coroutine. - Using
GlobalScope— it isn’t tied to anything and can leak; use a proper scope. - Doing UI updates off the main thread — switch back with
Dispatchers.Main.
Summary:suspendfunctions pause without blocking;launchfires work,async/awaitruns in parallel, and Dispatchers pick the thread. Always launch in a lifecycle-aware scope.