DevBits
DevBitsAndroid Handbook
Coroutines and Flow

What is the difference between launch and async?

Choose between launch and async based on what the caller needs back from the work.

COR-003Intermediate~45 sec

What the interviewer is testing

The interviewer is not really testing whether you remember that launch returns Job and async returns Deferred.

They want to know whether you can look at a piece of concurrent work and decide what the caller actually needs from it.

Does the caller only need the work to happen? Or does the next step depend on a value that work will produce?

A strong answer makes that decision clear first. Job, Deferred and await() are just the API details that follow from it.

❌ Common mistake

If they ask you to elaborate

Sometimes the work just needs to happen

Imagine a screen opens and your ViewModel needs to refresh some cached data.

The screen is not waiting for a value from that child coroutine. You just want the refresh to start and remain tied to the ViewModel lifecycle:

viewModelScope.launch {
    repository.refreshUser()
}

That is what launch communicates: start this work. The returned Job is there if you need to cancel it or observe when it finishes.

Sometimes the caller needs the results

Now imagine the screen needs a profile and a set of permissions before it can build its UI model.

Those requests do not depend on each other, so there is no reason to wait for one before starting the other:

val session = coroutineScope {
    val profile = async { profileRepository.loadProfile() }
    val permissions = async { permissionsRepository.loadPermissions() }

    UserSession(
        profile = profile.await(),
        permissions = permissions.await()
    )
}

Here async earns its place. Each child coroutine represents a result the caller needs, and the two independent operations can make progress at the same time.

The decision belongs to the caller

You can put side effects inside async, and you can ignore values inside launch, but neither makes the intent clearer.

Before choosing a builder, ask:

Does the caller need a value back from this work?

If not, launch is usually the clearer choice.

If yes—and especially if independent operations can overlap—async gives you a value you can await when you need it.

Production thinking

async is not the "more advanced" version of launch.

Every Deferred is a promise your code now has to keep.

Somebody has to await it.

Somebody owns its cancellation.

Somebody owns what happens if it fails.

That extra machinery is useful when it buys you something—for example, loading independent pieces of data at the same time before building a screen.

But if one operation needs the result of another, the work is still sequential:

val user = repository.loadUser()
val orders = repository.loadOrders(user.id)

Wrapping both calls in async would not remove that dependency.

The goal is not to use more coroutine builders. It is to make the relationship between the caller and the work obvious.

What separates a senior answer

Key takeaways

Learn more

On this page