What does start = CoroutineStart.LAZY change about async, and what is a subtle pitfall of lazy Deferreds in parallel decomposition?
answer
- LAZY = created, not started
- await() starts a lazy coroutine
- lazy + inline await = sequential
- fix with start() before await
- default DEFAULT is eager
basics
~20 sLAZY means the async coroutine doesn't start until you trigger it (via await() or start()). The pitfall: if you rely on await() to start lazy tasks, they run one at a time instead of in parallel.
solid answer
~40 sPassing start = CoroutineStart.LAZY to async makes the coroutine not begin immediately; it stays created-but-not-started until something triggers it — either await() or an explicit start(). This is useful to define work up front and run it on demand, or to conditionally skip it. The subtle pitfall for parallel decomposition: with LAZY, await() is what starts the coroutine. So `val a = async(start = LAZY){..}; val b = async(start = LAZY){..}; a.await() + b.await()` runs sequentially — a.await() starts AND finishes a before b is ever started. To regain concurrency you must explicitly start() each one first (a.start(); b.start()) or call awaitAll, then await. With the default DEFAULT start, eager launch avoids this trap entirely, which is why default async is the usual choice for fan-out.
code
kotlin · 4 linesval a = async(start = CoroutineStart.LAZY) { slowA() }
val b = async(start = CoroutineStart.LAZY) { slowB() }
a.start(); b.start() // start both first
val result = a.await() + b.await() // now concurrentgo deeper
Aware that a start parameter exists and LAZY delays execution.
Knows await() or start() triggers a lazy coroutine and that LAZY defers the body.
Identifies the inline-await serialization pitfall and fixes it with explicit start() or eager default.
Uses LAZY deliberately for task graphs/conditional work and reasons about trigger ordering and resource timing.
## `CoroutineStart` options `async` (and `launch`) accept a `start` parameter of type `CoroutineStart`: - `CoroutineStart.DEFAULT` — start **eagerly** (the usual case). - `CoroutineStart.LAZY` — create the coroutine but **do not start** it until triggered. - (`ATOMIC`, `UNDISPATCHED` exist for special cases.) ## What LAZY does ```kotlin val d = async(start = CoroutineStart.LAZY) { compute() } // compute() has NOT run yet d.start() // triggers it without waiting, OR val x = d.await() // triggers it AND waits ``` With `LAZY`, the body runs only after an explicit `start()` or the first `await()`. This lets you declare work in advance and decide later whether/when to run it (e.g., skip it on a cache hit). ## The parallel-decomposition pitfall Because `await()` itself triggers a lazy coroutine, awaiting lazily-started Deferreds **inline serializes them**: ```kotlin val a = async(start = CoroutineStart.LAZY) { slowA() } val b = async(start = CoroutineStart.LAZY) { slowB() } val sum = a.await() + b.await() // a starts, runs, finishes; THEN b starts ``` This runs in `tA + tB`, not `max`. With **eager** (`DEFAULT`) async, both bodies are already running before either `await()`, so you get `max`. ## Fixing lazy concurrency Start them explicitly before awaiting: ```kotlin val a = async(start = CoroutineStart.LAZY) { slowA() } val b = async(start = CoroutineStart.LAZY) { slowB() } a.start(); b.start() // both now running concurrently val sum = a.await() + b.await() ``` Or just use the default eager start, which is why default `async` is preferred for fan-out. ## When LAZY is genuinely useful - Define-now/run-later or conditional execution. - Building a graph of dependent tasks where you control trigger order. ## Key APIs/keywords - `start = CoroutineStart.LAZY` - `Deferred.start()` triggers without waiting - `await()` triggers + waits for a lazy coroutine - contrast `CoroutineStart.DEFAULT` (eager)
- What two things can trigger a LAZY async coroutine to start?An explicit start() call, or the first await() on its Deferred.
- Why is default (eager) async usually safer for parallel decomposition?Eager async starts both bodies before any await, so awaiting inline still overlaps; lazy needs explicit start() to avoid serialization.
saying these in an interview costs you the question
- Thinking LAZY async runs the body immediately
- Awaiting lazy Deferreds inline and expecting concurrency
- Not knowing await() can start a lazy coroutine
- Believing start() blocks/waits for completion (it only triggers)