Kotlin Coroutine Builders Explained: When to Use launch vs async

Table of Contents

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 Job that can be used for cancellation or waiting.
  • Runs in the background without blocking the main thread.
Kotlin
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"
}
  1. The coroutine is launched using launch(Dispatchers.IO) { ... }.
  2. The fetchData() function runs asynchronously without blocking the main thread.
  3. job.join() ensures the coroutine completes before proceeding.

Output:

Kotlin
Data: Fetched Data
Coroutine completed

Note: 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.
Kotlin
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"
}
  1. async(Dispatchers.IO) { fetchData() } starts an asynchronous task.
  2. It returns a Deferred<String> object.
  3. Calling .await() retrieves the result.

Output:

Kotlin
Data: Fetched Data

Important: 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

Kotlin
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,

  1. async { fetchData1() } and async { fetchData2() } start in parallel.
  2. Each coroutine takes 1 second.
  3. 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

  1. Use launch when you don’t need a result (e.g., updating UI, logging, background tasks).
  2. Use async when you need a result and always call await().
  3. For multiple async tasks, start them first, then call await() to maximize concurrency.
  4. Avoid using async outside 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 launch when you don’t need a result (fire-and-forget).
  • Use async when you need a result (and always call await()).

By understanding the differences and best practices, you can write efficient, safe, and scalable Kotlin applications using coroutines.

Skill Up: Software & AI Updates!

Receive our latest insights and updates directly to your inbox

Related Posts

error: Content is protected !!