Kotlin Coroutines

Kotlin Coroutines are a way to handle asynchronous programming in a simpler, more readable way. They let you write code that runs in the background (like fetching data from the internet) without blocking the main thread, making your app smooth and responsive. Think of coroutines as lightweight threads that are easier to manage.
Let’s break down the basics with simple explanations and examples.
1. What Are Coroutines?
Coroutines allow you to write asynchronous code that looks like synchronous code (sequential and easy to read).
They help with tasks like:
Fetching data from a server.
Reading/writing to a database.
Performing heavy computations without freezing the app.
Unlike threads, coroutines are cheaper and managed by Kotlin, so you don’t have to worry about low-level thread management.
2. Key Concepts
Here are the main building blocks of coroutines:
CoroutineScope: Defines the scope (or lifetime) of a coroutine. Every coroutine runs inside a scope.
launch: Starts a coroutine that runs in the background and doesn’t return a result.
async: Starts a coroutine that can return a result (useful when you need data back).
suspend: A keyword used for functions that can pause and resume later without blocking the thread.
Dispatchers: Tell coroutines which thread to run on:
Dispatchers.Main
: For UI-related tasks (e.g., updating the screen).Dispatchers.IO
: For network or file operations.Dispatchers.Default
: For CPU-intensive tasks (e.g., calculations).
3. Setting Up Coroutines
To use coroutines, add this dependency to your build.gradle
(Android or Kotlin project):
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.0"
For Android, also add:
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.0"
4. Basic Examples
Let’s walk through simple examples to understand how coroutines work.
Example 1: Using launch
to Run a Background Task
This example shows how to run a task in the background and update the UI.
import kotlinx.coroutines.*
import kotlin.coroutines.CoroutineContext
fun main() = runBlocking { // Creates a scope for coroutines
println("Main starts")
// Launch a coroutine in the background
launch(Dispatchers.Default) {
delay(1000) // Simulates a long task (1 second wait)
println("Background task done")
}
println("Main continues")
}
Output:
Main starts
Main continues
Background task done
Explanation:
runBlocking
creates a scope for coroutines in a console app (use it for testing).launch
starts a coroutine in theDispatchers.Default
context (good for CPU tasks).delay
is a suspend function that pauses the coroutine for 1 second without blocking the thread.The main program continues while the coroutine runs in the background.
Example 2: Using async
to Get a Result
This example shows how to use async
to compute something and get the result.
import kotlinx.coroutines.*
fun main() = runBlocking {
println("Starting calculation")
// Start an async coroutine
val deferredResult = async(Dispatchers.Default) {
delay(1000) // Simulate work
42 // Return a result
}
// Wait for the result
val result = deferredResult.await()
println("Result is $result")
}
Output:
Starting calculation
Result is 42
Explanation:
async
starts a coroutine that returns aDeferred
object (like a promise for a result).await()
waits for the result without blocking the thread.The coroutine computes
42
after a 1-second delay and returns it.
Example 3: Using suspend
Functions
Suspend functions are the heart of coroutines. They can pause and resume without blocking.
import kotlinx.coroutines.*
// A suspend function
suspend fun fetchData(): String {
delay(1000) // Simulate network call
return "Data from server"
}
fun main() = runBlocking {
println("Fetching data...")
val data = fetchData() // Call suspend function
println("Got: $data")
}
Output:
Fetching data...
Got: Data from server
Explanation:
suspend fun fetchData()
is a function that can pause (e.g., duringdelay
).It can only be called from a coroutine or another suspend function.
The main program waits for
fetchData
to complete before printing the result.
Example 4: Switching Dispatchers (Android Example)
This is a common Android use case: fetch data in the background and update the UI.
import kotlinx.coroutines.*
import android.widget.TextView
fun updateUI(textView: TextView) {
// Create a scope for coroutines
val scope = CoroutineScope(Dispatchers.Main)
scope.launch {
// Start on Main thread (UI)
textView.text = "Fetching data..."
// Switch to IO for network call
val data = withContext(Dispatchers.IO) {
delay(1000) // Simulate network
"Hello from server!"
}
// Back to Main to update UI
textView.text = data
}
}
Explanation:
CoroutineScope(Dispatchers.Main)
ensures the coroutine starts on the main thread.withContext(
Dispatchers.IO
)
switches to the IO thread for the network task.After getting the data, it switches back to the main thread to update the
TextView
.
5. Canceling Coroutines
You can cancel a coroutine if you no longer need it (e.g., when a user closes a screen).
import kotlinx.coroutines.*
fun main() = runBlocking {
val job = launch(Dispatchers.Default) {
repeat(5) { i ->
println("Task $i")
delay(500)
}
}
delay(1200) // Let it run for a bit
job.cancel() // Cancel the coroutine
println("Coroutine canceled")
}
Output:
Task 0
Task 1
Task 2
Coroutine canceled
Explanation:
launch
returns aJob
object that represents the coroutine.job.cancel()
stops the coroutine before it completes.This is useful for cleaning up tasks (e.g., stopping network calls when a user navigates away).
6. Best Practices
Always use a scope: Never launch coroutines without a
CoroutineScope
. UserunBlocking
for tests,viewModelScope
for ViewModels (Android), or custom scopes.Handle exceptions: Use
try-catch
or aCoroutineExceptionHandler
to handle errors.Cancel when done: Cancel coroutines when they’re no longer needed (e.g., when an Android Activity is destroyed).
Choose the right dispatcher: Use
Dispatchers.Main
for UI,Dispatchers.IO
for network/file, andDispatchers.Default
for computations.
7. Common Use Case: Android ViewModel
Here’s how you’d use coroutines in an Android ViewModel to fetch data.
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
class MyViewModel : ViewModel() {
fun fetchData(onResult: (String) -> Unit) {
viewModelScope.launch {
val result = withContext(Dispatchers.IO) {
// Simulate network call
Thread.sleep(1000) // Use delay in real coroutines
"Data from server"
}
onResult(result) // Update UI
}
}
}
Explanation:
viewModelScope
is a built-in scope for ViewModels that cancels coroutines when the ViewModel is cleared.withContext(
Dispatchers.IO
)
runs the network task in the background.The result is passed to
onResult
to update the UI.
8. Key Points to Remember
Coroutines make asynchronous code look synchronous, which is easier to read.
Use
launch
for fire-and-forget tasks,async
for tasks that return results.suspend
functions are non-blocking and can only be called from coroutines.Always manage coroutine lifecycles (scopes and cancellation) to avoid memory leaks.
9. Practice Exercise
Try this:
Write a program that:
Launches two coroutines: one to "fetch user data" (returns "User: Alice" after 1 second) and another to "fetch settings" (returns "Settings: Dark Mode" after 2 seconds).
Prints both results when they’re ready.
Use
async
andawait
to get the results.Cancel the "settings" coroutine after 1.5 seconds.
Solution:
import kotlinx.coroutines.*
fun main() = runBlocking {
val userDeferred = async {
delay(1000)
"User: Alice"
}
val settingsJob = async {
delay(2000)
"Settings: Dark Mode"
}
delay(1500) // Cancel settings after 1.5s
settingsJob.cancel()
try {
println(userDeferred.await())
println(settingsJob.await())
} catch (e: CancellationException) {
println("Settings fetch canceled")
}
}
Output:
User: Alice
Settings fetch canceled
This covers the basics of Kotlin Coroutines! Practice these examples, and let me know if you want to dive deeper into specific topics like structured concurrency, flow, or Android-specific use cases.
Subscribe to my newsletter
Read articles from Singaraju Saiteja directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

Singaraju Saiteja
Singaraju Saiteja
I am an aspiring mobile developer, with current skill being in flutter.