What is the difference between launch and async?
Choose between launch and async based on what the caller needs back from the work.
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.