What does the start parameter of launch control, and how does CoroutineStart.LAZY differ from CoroutineStart.DEFAULT?
answer
- start = WHEN the coroutine runs
- DEFAULT = dispatched immediately
- LAZY = waits for start() or join()
- ATOMIC = uncancellable until first suspend
- UNDISPATCHED = run now on current thread until first suspend
basics
~10 sThe start parameter decides when the coroutine actually begins running. DEFAULT starts it right away; LAZY creates the Job but waits to run until you trigger it with start() or join().
solid answer
~50 slaunch's second parameter is start: CoroutineStart, defaulting to CoroutineStart.DEFAULT. With DEFAULT, the coroutine is scheduled to run immediately (dispatched per its dispatcher). With CoroutineStart.LAZY, launch returns a Job in the 'New' state without executing the block; it only begins when you call job.start() or job.join() (or otherwise await it). LAZY is useful to build a Job ahead of time and trigger it later, or to set up a dependency graph. Other CoroutineStart values exist: ATOMIC (starts and is non-cancellable until the first suspension point — cannot be cancelled before it runs) and UNDISPATCHED (runs immediately in the current thread up to the first suspension, skipping the dispatcher). A common gotcha: a LAZY job that you never start and never join will simply never run, yet a structured parent will still wait for it as a child — so an unstarted lazy child can hang the scope unless started.
code
kotlin · 7 linesfun main() = runBlocking {
val lazy = launch(start = CoroutineStart.LAZY) { println("B: lazy ran") }
println("A: created")
lazy.start() // triggers it; without this it never runs
lazy.join()
println("C: done")
}go deeper
Knows DEFAULT runs now and LAZY waits, can trigger LAZY with start().
Explains start()/join() triggering, and the structured-concurrency hang gotcha for unstarted lazy children.
Distinguishes ATOMIC and UNDISPATCHED semantics and when each matters (guaranteed cleanup, low-latency start).
Weighs CoroutineStart choices against cancellation guarantees and dispatcher behavior in production code, and avoids subtle hangs/leaks from lazy children.
## The `start` parameter `launch` takes a `start: CoroutineStart` parameter: ```kotlin launch(start = CoroutineStart.LAZY) { /* ... */ } ``` `CoroutineStart` is an enum controlling **when and how** the coroutine begins executing. The default is `CoroutineStart.DEFAULT`. ## `DEFAULT` The coroutine is **immediately scheduled** for execution according to its dispatcher. 'Immediately scheduled' (not necessarily 'immediately running') — it's dispatched, so on `Dispatchers.Default`/`IO` it runs on a worker thread as soon as one is free. If it is cancelled before it starts, it may never run its body. ## `LAZY` The coroutine is **created but not started**. `launch` returns a `Job` in the *New* state; the block does not execute yet. It begins only when you explicitly trigger it: - `job.start()` — starts it and returns `true` if it was actually started. - `job.join()` — starts it (if not started) and then suspends until completion. ```kotlin fun main() = runBlocking { val job = launch(start = CoroutineStart.LAZY) { println("running now") } println("created but not running yet") job.start() // -> prints "running now" } ``` Use `LAZY` when you want to **prepare** a coroutine and decide later whether/when to run it, or to wire up ordered dependencies. ### LAZY gotcha with structured concurrency A lazy child you never start still belongs to the parent. A structured parent (e.g. `coroutineScope`) **waits for all children**, including an unstarted lazy one — but an unstarted lazy job never completes on its own, so the scope can **hang**. Always start lazy jobs (or don't make them children you forget). ## Other `CoroutineStart` values - **`ATOMIC`** — schedules like DEFAULT but the coroutine is **non-cancellable until it reaches its first suspension point**. So even if cancelled early, its body up to the first suspend runs. Useful to guarantee setup/cleanup code executes. - **`UNDISPATCHED`** — starts running **immediately in the current thread** until the first suspension point, *skipping* the dispatcher for that initial slice; after the first suspend it resumes on the dispatcher. Useful for low-latency immediate execution. ## Summary table - `DEFAULT`: dispatched immediately; cancellable before start. - `LAZY`: not started until `start()`/`join()`. - `ATOMIC`: dispatched immediately; not cancellable until first suspend. - `UNDISPATCHED`: runs now on current thread up to first suspend.
- What happens to a LAZY child that you never start, inside a coroutineScope?It never executes on its own, but the parent still treats it as a child and waits for it, so the scope can hang. You must call start() (or join()).
- Which two calls will start a LAZY job?job.start() and job.join() both start a lazy job; join() additionally suspends until it completes.
saying these in an interview costs you the question
- Saying LAZY runs the block but defers only the result
- Not knowing start() or join() triggers a LAZY job
- Claiming DEFAULT guarantees the block runs even if cancelled first
- Confusing UNDISPATCHED with LAZY