What problem do flatMapConcat, flatMapMerge, and flatMapLatest solve in Kotlin Flow, and how do they differ at a high level?
answer
- Flow<Flow<T>> -> flatten to Flow<T>
- Concat = sequential, ordered
- Merge = concurrent, interleaved
- Latest = cancel previous inner flow
- Merge default concurrency 16
basics
~20 sEach emitted value can itself produce a flow. These operators flatten those inner flows into one stream. Concat runs them one after another, Merge runs them at the same time, Latest cancels the old inner flow when a new value comes.
solid answer
~40 sWhen a transform on a Flow returns another Flow, you get a Flow<Flow<T>>. The flatMap* operators flatten that nested structure into a single Flow<T>. flatMapConcat collects each inner flow to completion before starting the next, preserving order (sequential). flatMapMerge collects multiple inner flows concurrently up to a concurrency limit (default DEFAULT_CONCURRENCY = 16), interleaving their emissions, so order is not guaranteed. flatMapLatest cancels the currently running inner flow as soon as the upstream emits a new value and starts a fresh inner flow for it — ideal for 'use only the latest input' cases like search-as-you-type. All three are intermediate operators, are cold, and run inside a suspend context, so no manual threading is needed.
code
kotlin · 10 linesimport kotlinx.coroutines.flow.*
import kotlinx.coroutines.delay
fun query(n: Int) = flow { delay(50L); emit(n * 10) }
suspend fun demo() {
flowOf(1, 2, 3).flatMapConcat { query(it) }.collect(::println) // 10,20,30 in order
flowOf(1, 2, 3).flatMapMerge { query(it) }.collect(::println) // 10,20,30 but order not guaranteed
flowOf(1, 2, 3).flatMapLatest { query(it) }.collect(::println) // likely just 30
}go deeper
Can state that the three flatten flows-of-flows and give the one-line difference (sequential / concurrent / cancel-previous).
Knows map produces a nested Flow, names flattenConcat/flattenMerge, and picks the right operator per scenario.
Discusses ordering guarantees, the concurrency parameter and its default, and cancellation/structured-concurrency implications.
Reasons about backpressure, resource exhaustion from unbounded concurrency, and when a custom transform/channelFlow beats a stock operator.
## The problem: flows of flows A `Flow<T>` is a cold asynchronous stream. Sometimes a transformation of each element itself produces a `Flow`. For example, for each user id you call `fetchProfile(id): Flow<Profile>`. If you use `map`, you get a `Flow<Flow<Profile>>` — a nested flow. **Flattening** operators turn `Flow<Flow<T>>` into a single `Flow<T>`. Kotlin offers three flattening strategies: ## flatMapConcat — sequential Collects each inner flow **to completion** before subscribing to the next one. Emissions stay in upstream order. Equivalent to `map { ... }.flattenConcat()`. ## flatMapMerge — concurrent Subscribes to several inner flows **at once** and merges their emissions as they arrive, so order is interleaved/non-deterministic. It takes a `concurrency` parameter limiting how many inner flows run simultaneously (default `DEFAULT_CONCURRENCY` = 16, overridable via the `kotlinx.coroutines.flow.defaultConcurrency` system property). ## flatMapLatest — keep only the newest When upstream emits a new value, the **currently running inner flow is cancelled** and a new one starts. Only the latest input's inner flow survives. Built on `transformLatest`. ```kotlin import kotlinx.coroutines.flow.* import kotlinx.coroutines.delay val source = flowOf(1, 2, 3) fun inner(n: Int) = flow { delay(100L); emit("$n-a"); delay(100L); emit("$n-b") } source.flatMapConcat { inner(it) } // 1-a,1-b,2-a,2-b,3-a,3-b (ordered) source.flatMapMerge { inner(it) } // interleaved: 1-a,2-a,3-a,1-b,... source.flatMapLatest { inner(it) } // only 3-a,3-b (1 and 2 cancelled) ``` ## Key terms - **Cold**: nothing runs until a terminal operator (e.g. `collect`) subscribes. - **Intermediate operator**: returns a new Flow, runs lazily, does not start collection. - **Inner flow**: the flow returned by your lambda for each upstream value. - **Concurrency**: number of inner flows collected at the same time. All three are `suspend`-friendly and structured-concurrency aware: cancelling the collector cancels the inner flows.
- If your lambda returns a Flow and you use map instead of flatMapConcat, what type do you get?Flow<Flow<T>> — a nested flow you'd then have to flatten yourself with flattenConcat/flattenMerge.
- Which one would you pick for search-as-you-type?flatMapLatest, so each new keystroke cancels the in-flight request for the previous query.
Concat = one checkout lane served fully before the next; Merge = several lanes open at once; Latest = abandon your half-rung order the moment a new one arrives.
saying these in an interview costs you the question
- Thinking flatMapMerge preserves emission order
- Confusing flatMap* with map (not realizing map gives nested flows)
- Believing flatMapConcat runs inner flows in parallel
- Saying flatMapLatest waits for the old inner flow to finish before cancelling
- Claiming these are terminal operators that start collection