← All courses

Coroutines & Asynchronous Programming

🗓 May 31, 2026 ⏱ 2 min read

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 you await().

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 suspend function 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: suspend functions pause without blocking; launch fires work, async/await runs in parallel, and Dispatchers pick the thread. Always launch in a lifecycle-aware scope.