What is onEach and how does it differ from collect? Why is onEach often paired with launchIn, and what does map do that onEach intentionally does not?
answer
- onEach = side effect, value passes through
- intermediate (returns Flow) vs collect terminal
- onEach + launchIn = scope.launch { collect() }
- launchIn returns a cancellable Job
- map transforms; onEach observes
basics
~20 sonEach runs a side effect for each value but passes the value through unchanged, returning a Flow. collect is terminal and returns Unit. onEach + launchIn lets you start the flow in a scope without writing a collect lambda.
solid answer
~40 sonEach { } is an intermediate operator that invokes a suspend side-effecting action per value and re-emits the value unchanged, so the chain continues. collect { } is a terminal operator that consumes the flow and returns Unit — nothing can follow it. The common pattern flow.onEach { update(it) }.launchIn(scope) replaces scope.launch { flow.collect { update(it) } }: launchIn is terminal, starts collection in the given CoroutineScope, and returns a Job you can cancel. Unlike map, onEach does not change the emitted value (its lambda returns Unit and the original element flows on); use map to transform, onEach to log/update/trigger effects mid-chain. onEach also runs before terminal collection, making it handy for instrumentation between operators.
code
kotlin · 5 linesval job: Job = ticks
.onEach { tick -> render(tick) } // value unchanged
.onCompletion { cause -> log("done: $cause") }
.launchIn(scope) // terminal, starts collection
// later: job.cancel()go deeper
Knows onEach does a side effect and the value passes through unchanged.
Distinguishes intermediate onEach from terminal collect and knows the value is not transformed.
Explains onEach + launchIn replacing scope.launch { collect() }, the returned Job, and declarative catch/onCompletion pipelines.
Advocates the operator-pipeline style for testability/cancellation and reasons about onEach placement relative to map for instrumentation.
## onEach: a pass-through side effect ```kotlin public fun <T> Flow<T>.onEach(action: suspend (T) -> Unit): Flow<T> ``` For every value, `onEach` runs `action` (a `suspend` lambda) and then **re-emits the same value** unchanged. It returns a `Flow<T>`, so it is **intermediate** — you can keep chaining: ```kotlin flow.onEach { log.debug("got $it") } .map { it.id } .collect { save(it) } ``` Contrast with `map`, whose lambda returns `R` and changes the value. `onEach`'s lambda returns `Unit`; the element is forwarded as-is. So: **map transforms, onEach observes.** ## onEach vs collect - `collect` is **terminal**: it returns `Unit`, drives the flow, and nothing can follow it. - `onEach` is **intermediate**: it returns a flow and runs nothing by itself until a terminal operator pulls. ```kotlin // These two run the same side effect, but only the second keeps a Flow: flow.collect { handle(it) } // terminal, ends the chain flow.onEach { handle(it) } /* still a Flow, needs a terminal */ ``` ## onEach + launchIn `launchIn(scope)` is a terminal operator equivalent to `scope.launch { collect() }`: ```kotlin flow .onEach { uiState.value = it } // side effect per value .catch { e -> log.error(e) } // handle errors in-chain .launchIn(viewModelScope) // returns a Job ``` This declarative style is idiomatic in Android ViewModels: you describe the pipeline with operators (`onEach`, `catch`, `onCompletion`) and `launchIn` starts it in a scope, returning a cancellable `Job`. It avoids nesting a `collect { }` lambda and keeps error handling as operators. ## Ordering note Because `onEach` is positional, an `onEach` placed before `map` sees the original value; placed after, it sees the transformed value. Use it to instrument any point in the pipeline.
- Is launchIn an intermediate or terminal operator?Terminal. It starts collection in the supplied scope and returns a Job; you cannot chain further operators after it.
- If you only need a side effect and then nothing else, should you use onEach or collect?A plain collect { } is simpler. Use onEach when you want to keep the chain (more operators) or pair it with launchIn for a declarative, cancellable pipeline.
onEach is a security camera in the hallway — it records who passes but doesn't change them; collect is the door at the end where they actually leave.
saying these in an interview costs you the question
- Saying onEach transforms the value like map
- Calling onEach terminal (it returns a Flow)
- Thinking launchIn is intermediate or returns the flow
- Believing onEach by itself runs the flow without a terminal operator
- Forgetting launchIn returns a Job you can cancel