What does launchIn(scope) do, and how does it differ from calling collect? When would you prefer it, and what are the lifecycle implications?
answer
- launchIn = scope.launch { collect() }
- Non-suspending; returns a Job
- Pair with onEach / catch / onCompletion (no lambda itself)
- Lifetime bound to the scope; cancel scope/Job to stop
- Use for fire-and-forget + concurrent collectors
basics
~20 slaunchIn(scope) starts collecting a flow in the background inside the given scope and returns a Job, instead of suspending the current code like collect does. It's used for fire-and-forget collection tied to a scope's lifecycle.
solid answer
~40 scollect is a suspend function: it runs collection in the CURRENT coroutine and suspends until the flow completes, so it blocks the rest of your sequential code. launchIn(scope) is shorthand for scope.launch { collect() }: it is non-suspending, launches a new coroutine in the supplied CoroutineScope, returns a Job, and returns immediately so following code runs concurrently. You typically pair it with onEach/onCompletion/catch to express side effects, since launchIn itself takes no lambda: flow.onEach { handle(it) }.launchIn(scope). The flow's lifetime is now bound to that scope — cancelling the scope (or the returned Job) cancels collection. This is the standard pattern for observing flows from a lifecycleScope/viewModelScope, or for starting multiple independent collectors that should run in parallel.
code
kotlin · 12 linesclass MyViewModel(repo: Repo) : ViewModel() {
init {
repo.eventsFlow()
.onEach { event -> handle(event) }
.catch { e -> logError(e) }
.launchIn(viewModelScope) // non-blocking; cancelled in onCleared()
repo.statusFlow()
.onEach { status -> update(status) }
.launchIn(viewModelScope) // runs CONCURRENTLY with the above
}
}go deeper
Knows launchIn starts a flow in the background and collect runs it inline.
Explains launchIn returns a Job, is non-suspending, and needs onEach for side effects.
Ties collection lifetime to the scope, uses launchIn for concurrent collectors, and routes errors via catch/onCompletion.
Designs scope ownership and cancellation boundaries across layers, reasons about exception propagation through the scope Job, and decides where flow observation belongs architecturally.
## collect vs launchIn `collect` is the fundamental **suspend** terminal: ```kotlin suspend fun consume(f: Flow<Int>) { f.collect { println(it) } // suspends HERE until f completes println("done") // runs only after collection finishes } ``` It runs in the **current** coroutine and blocks the sequential flow of code. `launchIn` is a thin convenience defined roughly as: ```kotlin public fun <T> Flow<T>.launchIn(scope: CoroutineScope): Job = scope.launch { collect() } ``` Key properties: - **Non-suspending** — the only terminal that does not suspend the caller. - Launches a **new coroutine** in `scope` and returns a **`Job`** immediately. - Collection runs **concurrently** with whatever follows. ## It takes no consumer lambda `launchIn` calls the **parameterless** `collect()`. To do something per element, attach an **intermediate** operator first — usually `onEach`: ```kotlin flow .onEach { value -> render(value) } // side effect per element .onCompletion { cause -> log(cause) } // on finish/cancel .catch { e -> reportError(e) } // upstream errors .launchIn(viewModelScope) // start, return Job ``` This 'declarative' style reads top-to-bottom and is idiomatic for UI observation. ## Lifecycle / cancellation The returned `Job` and the supplied **`CoroutineScope`** govern lifetime: - Cancelling the **scope** (e.g. `viewModelScope` on `onCleared()`, `lifecycleScope` on destroy) cancels the collector and stops the upstream. - Cancelling the **returned Job** stops just that one collection. - Structured concurrency means you don't leak collectors as long as the scope is properly scoped. ```kotlin val job = ticker.onEach { tick() }.launchIn(scope) // ...later... job.cancel() // stop this collector only ``` ## When to prefer launchIn - You want **fire-and-forget** observation that should not block the surrounding code. - You need **multiple concurrent collectors** (each `launchIn` is its own coroutine running in parallel): ```kotlin flowA.onEach { ::a }.launchIn(scope) flowB.onEach { ::b }.launchIn(scope) // both collect concurrently ``` Doing this with sequential `collect` calls would block on the first flow forever. - You're tying a long-lived/hot flow (e.g. `StateFlow`) to a UI scope. ## When to prefer collect - You need the result **before continuing** (sequential logic, `toList`, awaiting completion). - You want the simplest single-consumer side effect inside an existing coroutine. ## Gotcha Because `launchIn` returns immediately, exceptions in the flow are **not** thrown to the caller — handle them with `catch`/`onCompletion` or a `CoroutineExceptionHandler` on the scope, or they propagate through the scope's job and may cancel it.
- Why attach onEach before launchIn instead of passing a lambda to launchIn?launchIn takes no consumer lambda — it calls the parameterless collect(). onEach is the intermediate operator that holds the per-element side effect.
- How are exceptions from a flow handled when started with launchIn?They are not thrown to the caller. Use a catch operator (or onCompletion / a CoroutineExceptionHandler on the scope); otherwise they propagate through the scope's Job and can cancel it.
collect is making a phone call and staying on the line; launchIn is hiring an assistant to take the call so you can keep working.
saying these in an interview costs you the question
- Thinking launchIn takes a collector lambda
- Saying launchIn suspends the caller
- Forgetting collection lifetime is tied to the scope
- Using sequential collect calls when concurrent collection was needed
- Assuming exceptions surface to the calling code with launchIn