Kotlin coroutines are a powerful feature that simplify asynchronous programming. They allow developers to write asynchronous code in a sequential manner, making it easier to read and maintain. At the heart of coroutines are coroutine builders, specifically launch and async. These builders define how coroutines are started and managed.
In this blog, we’ll dive deep into the differences between launch and async, when to use them, and best practices for effective coroutine usage.
Understanding Coroutine Builders
A coroutine builder is a function that creates and starts a coroutine. The two primary coroutine builders in Kotlin are:
- launch: Used for fire-and-forget operations (does not return a result).
- async: Used for parallel computations that return a result.
Let’s explore both in detail.
launch – Fire and Forget
The launch builder is used when you don’t need a result. It starts a coroutine that runs independently, meaning it does not return a value. However, it returns a Job object, which can be used to manage its lifecycle (e.g., cancellation or waiting for completion).
Key Points:
- Does not return a result.
- Returns a Jobthat can be used for cancellation or waiting.
- Runs in the background without blocking the main thread.
import kotlinx.coroutines.*
fun main() = runBlocking {
    val job =
        launch(Dispatchers.IO) {
            val data = fetchData()
            println("Data: $data")
        }
    job.join() // Ensures the coroutine completes before proceeding
    println("Coroutine completed")
}
suspend fun fetchData(): String {
    delay(1000) // Simulating network call
    return "Fetched Data"
}- The coroutine is launched using launch(Dispatchers.IO) { ... }.
- The fetchData()function runs asynchronously without blocking the main thread.
- job.join()ensures the coroutine completes before proceeding.
Output:
Data: Fetched Data
Coroutine completedNote: If
job.join()is not called, the program might exit before the coroutine completes, depending on the coroutine scope.
async – Returns a Result
The async builder is used when you need a result. It returns a Deferred<T> object, which represents a future result. To retrieve the result, you must call await().
Key Points:
- Returns a Deferred<T>object, which must be awaited.
- Used for parallel computations.
- Must be called within a coroutine scope.
import kotlinx.coroutines.*
fun main() = runBlocking {
    val result = async(Dispatchers.IO) { fetchData() }
    println("Data: ${result.await()}") // Await to get the result
}
suspend fun fetchData(): String {
    delay(1000) // Simulating network call
    return "Fetched Data"
}- async(Dispatchers.IO) { fetchData() }starts an asynchronous task.
- It returns a Deferred<String>object.
- Calling .await()retrieves the result.
Output:
Data: Fetched DataImportant: If you do not call
await(), the coroutine will run, but the result won’t be retrieved.
async for Parallel Execution
One of the most powerful use cases for async is running multiple tasks in parallel.
Running Two Tasks in Parallel
import kotlinx.coroutines.*
fun main() = runBlocking {
    val startTime = System.currentTimeMillis()
    val deferred1 = async { fetchData1() }
    val deferred2 = async { fetchData2() }
    val result1 = deferred1.await() // Waits for fetchData1
    val result2 = deferred2.await() // Waits for fetchData2
    val endTime = System.currentTimeMillis()
    println("Results: $result1, $result2")
    println("Total time: ${endTime - startTime} ms")
}
suspend fun fetchData1(): String {
    delay(1000) // Simulating API call
    return "Data 1"
}
suspend fun fetchData2(): String {
    delay(1000) // Simulating API call
    return "Data 2"
}Here,
- async { fetchData1() }and- async { fetchData2() }start in parallel.
- Each coroutine takes 1 second.
- Since they run concurrently, the total execution time is ~1 second instead of 2 seconds (if they run sequentially then 2 seconds).
Best Practices for launch and async
- Use launchwhen you don’t need a result (e.g., updating UI, logging, background tasks).
- Use asyncwhen you need a result and always callawait().
- For multiple async tasks, start them first, then call await()to maximize concurrency.
- Avoid using asyncoutside of structured concurrency unless you explicitly manage its lifecycle, as it can lead to untracked execution, potential memory leaks, or uncaught exceptions.
Conclusion
Kotlin’s launch and async coroutine builders serve distinct purposes:
- Use launchwhen you don’t need a result (fire-and-forget).
- Use asyncwhen you need a result (and always callawait()).
By understanding the differences and best practices, you can write efficient, safe, and scalable Kotlin applications using coroutines.

