Kotlin Coroutines: async/await for Parallel Results

async starts a coroutine for a parallel result, returning a `Deferred` value. `await` pauses until that result is ready. Use it to run independent network calls concurrently. The footgun: using it for sequential tasks creates a needless race condition.
WHY IT EXISTS: To run multiple long-running tasks concurrently and wait for all of them to finish before using their combined results. Without it, you would have to run tasks sequentially, where task B only starts after task A finishes. This is inefficient if the tasks are independent and you need both results to proceed.
THE MENTAL MODEL: Think of async as ordering food from two different food trucks at the same time. You get a ticket (a Deferred object) from each. You can wander around until you're ready to eat. await is when you go to the truck window and wait for them to hand you your food. You placed both orders in parallel to save time, and the total wait is only as long as the slowest truck, not the sum of both waits.
HOW IT WORKS: async is a coroutine builder that starts a new coroutine. It immediately returns a Deferred<T>, which is a promise for a value of type T that will be available in the future. The code inside the async block runs concurrently with other code. Calling await() on a Deferred object suspends the current coroutine until the async block completes and its result is returned. If an exception occurs inside async, it is stored and re-thrown when await() is called.
WHEN TO USE IT: Use async when you have two or more independent, long-running tasks (like network requests or database queries) and you need the results of all of them to proceed. Running them in parallel with async can significantly reduce the total time a user has to wait. This is a common pattern for enriching a UI screen with data from multiple sources.
WHEN NOT TO USE IT: Do not use async for "fire-and-forget" tasks where you don't need a result; use launch for that. Do not use it for sequential operations where the input of one task is the output of another. Chaining them with async is an anti-pattern that adds complexity for no benefit; just write sequential suspending calls instead.
ONE CANONICAL EXAMPLE: In an Android ViewModel, you might need to fetch a user's profile and their list of friends from two different API endpoints. Inside a coroutineScope, you would create two Deferred values: val profileDeferred = async { api.fetchProfile() } and val friendsDeferred = async { api.fetchFriends() }. These two network calls start concurrently. Afterwards, you retrieve the results by calling await(): val profile = profileDeferred.await() and val friends = friendsDeferred.await(). The total time is now the duration of the longer call, not the sum of both.
Read the original → developer.android.com
Get five bites like this every day.
Tezvyn delivers a daily feed of 60-second tech bites with quizzes to lock in what you learn.