skip to content

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?

level: seniorimportance: should knowfreq 40%

answer

  1. onEach = side effect, value passes through
  2. intermediate (returns Flow) vs collect terminal
  3. onEach + launchIn = scope.launch { collect() }
  4. launchIn returns a cancellable Job
  5. map transforms; onEach observes

basics

~20 s

onEach 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 s

onEach { } 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 lines
kotlin
val job: Job = ticks
    .onEach { tick -> render(tick) }   // value unchanged
    .onCompletion { cause -> log("done: $cause") }
    .launchIn(scope)                    // terminal, starts collection
// later: job.cancel()

go deeper

for a junior

Knows onEach does a side effect and the value passes through unchanged.

for a middle

Distinguishes intermediate onEach from terminal collect and knows the value is not transformed.

for a senior

Explains onEach + launchIn replacing scope.launch { collect() }, the returned Job, and declarative catch/onCompletion pipelines.

for a principal

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

context