launch vs async

Coroutines Junior Kotlin 2.0+ API 24+ Reviewed By Best Latest Apps

Predict the output

Each call takes 1 second. Roughly how long until both are printed?

fun main() = runBlocking {
    val start = System.currentTimeMillis()
    val a = async { delay(1_000); "A" }
    val b = async { delay(1_000); "B" }
    println(a.await() + b.await())
    println((System.currentTimeMillis() - start) / 1_000)
}
Options
Hint 1 of 2

When does each async block start?

Hint 2 of 2

Do the two delays overlap?

Choose an answer, then open Show answer below to check it.

Show answer

Answer: A AB, then 1

Correct. Both async blocks start straight away and wait at the same time.

Why not the others?
  • B AB, then 2. That would happen if b started only after a.await() returned.
  • C BA, then 1. The string is built in the order of the awaits, so A comes first.
  • D It prints 0 because async doesn't start until await. async starts eagerly unless you pass CoroutineStart.LAZY.
Timeline: two async calls overlap and finish at 1 s; awaiting each immediately runs them back to back and finishes at 2 s.0 s0.5 s1 s1.5 s2 sTwo async, then awaitasync Aasync B1 sasync { }.await() twiceAB2 s
Timeline: two async calls overlap and finish at 1 s; awaiting each immediately runs them back to back and finishes at 2 s.
Text version
Timeline as text, elapsed time t in seconds
VersionWhat runs whenFinishes at
Two async, then await
  • async A from t = 0.0 s to t = 1.0 s
  • async B from t = 0.0 s to t = 1.0 s
t = 1.0 s
async { }.await() twice
  • A from t = 0.0 s to t = 1.0 s
  • B from t = 1.0 s to t = 2.0 s
t = 2.0 s

30-second answer

launch starts a coroutine for its side effect and returns a Job. async starts one that produces a value and returns a Deferred you await. Use async only when you need results from parallel calls; two async calls awaited later run at the same time, while two sequential suspend calls do not.

Practise saying it · 45 s. No recording, just a timer

Explain launch vs async with a timing example.

Say your answer out loud, then check which of these points you covered:

  • launch returns a Job for side effects
  • async returns a Deferred value
  • Two asyncs awaited later run in parallel
  • Exceptions in async still cancel the parent

Deep dive

Both start immediately by default (CoroutineStart.DEFAULT). Calling await only waits for a result that is already being computed.

Calling async { }.await() straight away is the same as a plain sequential call, with extra noise.

An exception in launch propagates to the parent at once. An exception in async also cancels the parent in a normal scope; await rethrows it.

For a list of inputs, map to async and call awaitAll().

Common mistakes

  • Using async for fire-and-forget work and never awaiting it.
  • Writing async { a() }.await() and async { b() }.await() back to back and expecting parallelism.
  • Thinking an exception in async stays hidden until await in a regular scope.

Senior take & follow-ups

A strong answer gives the timing: sequential awaits add up, parallel ones take the longest single call. Then it mentions the exception behaviour.

Follow-up questions

Think of your answer first, then open the hint.

  1. How would you cap parallelism for 100 IDs?

    Show hint

    Use a Semaphore, or limitedParallelism on the dispatcher.

  2. What does start = CoroutineStart.LAZY change?

    Show hint

    The coroutine waits until start() or await() is called.

Compare on iOS: Sequential await vs async let async let in Swift and async in Kotlin both start a child at once; the scope rules match.

Suggest a fix if something here is wrong or out of date.