async: What It Is, Deferred and Parallel Coroutines How They Work

Author: IT Sectr Published: 2026-06-22 Reading time: 8 min

async — a Coroutine Builder in Kotlin that launches a coroutine and returns a Deferred<T> object for obtaining the result of an asynchronous operation. async allows executing multiple tasks in parallel and aggregating their results via await(). According to JetBrains guide, 2024, async is the preferred way to organize parallel computations in coroutines.

Key Takeaways

  • async — a coroutine builder returning Deferred<T> with the result of an asynchronous operation
  • Deferred — an asynchronous promise, inheriting Job, with the await() method to obtain the result
  • async allows executing independent tasks in parallel, reducing overall time
  • await() — a suspend function that suspends the coroutine until the result is obtained without blocking
  • Structured concurrency with async guarantees cancellation of all parallel tasks on error

What is async in Kotlin Coroutines?

async — is a CoroutineScope extension function that creates a coroutine returning a result. Unlike launch, async returns Deferred<T> — an object representing a future value. A coroutine launched via async executes concurrently with other coroutines, allowing computations to be parallelized.

async is used when the result of an asynchronous operation is needed for further processing: loading data from multiple sources, batch processing collections, parallel requests to various APIs. Deferred is created immediately, and the result becomes available after the coroutine completes.

kotlin
import kotlinx.coroutines.*

suspend fun String.delayedValue(): String {
    delay(1000L)
    return this
}

fun main() = runBlocking {
    val deferred: Deferred<String> = async { "Hello".delayedValue() }
    println(deferred.await())
}

async and Deferred Syntax

The async signature is similar to launch: the same CoroutineContext, CoroutineStart parameters, and a suspend block. The difference is in the return type — Deferred<T> instead of Job, where T is the result type returned by the last expression of the block.

kotlin
public fun <T> CoroutineScope.async(
    context: CoroutineContext = EmptyCoroutineContext,
    start: CoroutineStart = CoroutineStart.DEFAULT,
    block: suspend CoroutineScope.() -> T
): Deferred<T>

Deferred<T> — an interface inheriting Job and adding await(), getCompleted(), getCompletionExceptionOrNull() methods. await() is a suspend function and can only be called inside a coroutine or another suspend function. getCompleted() is a non-blocking method that throws an exception if the coroutine has not yet completed.

Parallel Tasks with async

The main advantage of async is the ability to run tasks in parallel. Instead of waiting for each operation sequentially (total time = sum of times), async launches coroutines simultaneously, reducing total time to the duration of the longest operation.

Parallel Data Loading

A typical scenario — loading a user profile, their settings, and order history with three parallel async requests. After all three complete, the results are combined into a single data model.

kotlin
suspend fun loadUserDashboard(userId: String): Dashboard = coroutineScope {
    val profile = async { api.getProfile(userId) }
    val settings = async { api.getSettings(userId) }
    val orders = async { api.getRecentOrders(userId) }

    Dashboard(
        profile = profile.await(),
        settings = settings.await(),
        orders = orders.await()
    )
}

The coroutineScope function launches child coroutines and waits for their completion before returning the result. The entire block executes in parallel, not sequentially.

Handling Deferred Results

Deferred supports several methods for working with the result. In addition to await(), there are methods for checking status, getting a completed value, and aggregating multiple Deferreds through Kotlin Coroutines utility functions.

MethodDescriptionSuspend?
await()Suspends the coroutine until the result is obtainedYes
getCompleted()Returns the result if the coroutine has completed (otherwise Exception)No
getCompletionExceptionOrNull()Returns the exception on error or nullNo
isCompletedChecks whether the coroutine has completedNo

For aggregating a list of Deferreds, awaitAll() is used — a function that suspends execution until all Deferreds in the collection complete. If at least one completed with an error, awaitAll() throws the exception.

kotlin
suspend fun <T> loadAll(requests: List<suspend () -> T>): List<T> {
    return coroutineScope {
        val deferreds = requests.map { async { it() } }
        deferreds.awaitAll()
    }
}

async vs launch Differences

The choice between async and launch depends on whether the coroutine result is needed. launch returns Job and is suitable for fire-and-forget operations, async returns Deferred and is used for tasks that return data. In terms of syntax and parameters, the builders are identical.

  • launch → Job (no result), async → Deferred<T> (result T)
  • launch — fire-and-forget tasks (logging, caching, sending analytics)
  • async — parallel computations (loading from multiple APIs, batch processing)
  • Both support structured concurrency and CoroutineStart parameters
  • await() is only available for Deferred, launch does not provide a result

Important rule: never use async for fire-and-forget. If the coroutine result is not needed, use launch. async creates overhead for creating Deferred, which is not justified in such scenarios.

async Examples in Mobile Development

In Android, async is used for parallel operations inside ViewModel and UseCases. Typical scenarios: loading profile and news feed simultaneously, fetching weather from multiple weather services, batch data synchronization with the server.

kotlin
class HomeViewModel : ViewModel() {
    fun loadHomeScreen() {
        viewModelScope.launch {
            val userData = async(Dispatchers.IO) { repository.getUserData() }
            val newsFeed = async(Dispatchers.IO) { repository.getNewsFeed() }
            val notifications = async(Dispatchers.IO) { repository.getNotifications() }

            val state = HomeState(
                user = userData.await(),
                news = newsFeed.await(),
                unread = notifications.await()
            )
            _uiState.update { state }
        }
    }
}

It is recommended to wrap a group of async calls in coroutineScope or supervisorScope for error isolation. supervisorScope allows the remaining async coroutines to continue even if one fails — useful for non-critical operations.

Frequently Asked Questions

Can async be used without await?

It can, but it is pointless. async without await() launches a coroutine but its result is lost. If the result is not needed, use launch — it does not create a Deferred and is more efficient in terms of memory and performance.

How does async handle exceptions?

An exception inside async is stored in Deferred. Calling await() throws this exception. To check without throwing, use getCompletionExceptionOrNull(). When using coroutineScope, an error cancels all child coroutines.

What is awaitAll in Kotlin Coroutines?

awaitAll() — an extension function for Iterable<Deferred<T>> that suspends the coroutine until all Deferreds complete. Returns a list of results. If at least one Deferred fails, it throws the exception and cancels the rest.

How is async different from GlobalScope.async?

Yes. async is a CoroutineScope extension function tied to the scope. GlobalScope.async creates a coroutine without lifecycle binding — it can run indefinitely. GlobalScope is not recommended in Android due to the risk of leaks.

How many async coroutines can be launched for parallel requests?

The number is dictated by the dispatcher: Dispatchers.IO supports up to 64 concurrent threads, Dispatchers.Default — up to the number of CPU cores. For bulk operations, use limited parallelism via Semaphore or mapNotNull with awaitAll.

Summary

  • async — a coroutine builder returning Deferred<T> for asynchronous result retrieval
  • Deferred inherits Job and adds await(), getCompleted(), getCompletionExceptionOrNull() methods
  • Parallel execution with async reduces total time to the duration of the longest operation
  • awaitAll() — a utility for waiting for all Deferreds in a collection
  • coroutineScope — recommended wrapper for a group of async calls with cancellation guarantee
  • Do not use async for fire-and-forget — use launch instead
  • In Android, async is used for parallel loading in ViewModel with viewModelScope

We will develop a mobile application turnkey

IT Sectr creates iOS and Android applications for startups and businesses since 2017. We will advise you and propose the best solution.

Discuss the project

Read also