skip to content

What are flattenConcat/flattenMerge, how do they relate to the flatMap* operators, and what should you know about the experimental/stability status of these operators?

level: seniorimportance: nice to knowfreq 30%

answer

  1. flatMap* = map + flatten*
  2. flattenConcat sequential, flattenMerge concurrent
  3. No flattenLatest exists
  4. Opt-in: @FlowPreview / @ExperimentalCoroutinesApi
  5. Verify stability per coroutines version

basics

~10 s

flattenConcat and flattenMerge flatten an existing Flow<Flow<T>>. flatMapConcat/Merge are just map-then-flatten shortcuts. Some of these operators were experimental and need an opt-in annotation; check your kotlinx-coroutines version.

solid answer

~30 s

If you already have a Flow<Flow<T>> (e.g. from map), flatten it directly with flattenConcat() (sequential) or flattenMerge(concurrency) (concurrent). The flatMap* operators are conveniences: flatMapConcat == map{...}.flattenConcat(); flatMapMerge == map{...}.flattenMerge(concurrency); flatMapLatest == transformLatest { emitAll(transform(it)) }. Historically several Flow operators were marked with @FlowPreview or @ExperimentalCoroutinesApi, requiring an opt-in annotation (@OptIn) or compiler flag, and signatures could change between versions. Treat the exact stability as version-dependent: verify in the kotlinx-coroutines version you ship, and add the opt-in if the compiler asks. There is no flattenLatest; the 'latest' family is expressed via flatMapLatest/transformLatest. Prefer the stable, current operator and consult your version's API.

code

kotlin · 9 lines
kotlin
import kotlinx.coroutines.flow.*

val ids = flowOf(1, 2, 3)
fun load(id: Int) = flowOf("a$id", "b$id")

// flatMapConcat is sugar over map + flattenConcat
val viaFlatMap = ids.flatMapConcat { load(it) }
val viaFlatten = ids.map { load(it) }.flattenConcat()
// both yield: a1,b1,a2,b2,a3,b3

go deeper

for a junior

Knows flatMap* flattens nested flows and that flatten* operate on an existing Flow<Flow<T>>.

for a middle

States the map+flatten equivalences and that there is no flattenLatest.

for a senior

Explains opt-in annotations (@FlowPreview/@ExperimentalCoroutinesApi), @OptIn, and version-dependent stability.

for a principal

Sets team policy on opt-in usage, version pinning, and migration when experimental operators stabilise or change signatures.

## flatten* vs flatMap* When you already hold a `Flow<Flow<T>>`, use the flatten operators: - `flattenConcat()` — sequential, like concat. - `flattenMerge(concurrency = DEFAULT_CONCURRENCY)` — concurrent, like merge. The `flatMap*` operators are defined in terms of map + flatten (or transform): ```kotlin // Conceptual equivalences flow.flatMapConcat(transform) == flow.map(transform).flattenConcat() flow.flatMapMerge(c, transform) == flow.map(transform).flattenMerge(c) flow.flatMapLatest(transform) == flow.transformLatest { emitAll(transform(it)) } ``` There is **no** `flattenLatest`; the 'latest' semantics live only in `flatMapLatest` / `mapLatest` / `transformLatest`. ```kotlin import kotlinx.coroutines.flow.* val nested: Flow<Flow<Int>> = flowOf(flowOf(1, 2), flowOf(3, 4)) val seq = nested.flattenConcat() // 1,2,3,4 ordered val par = nested.flattenMerge(concurrency = 2) // interleaved ``` ## Stability / opt-in annotations Kotlin marks not-yet-stable APIs with **opt-in annotations**: - `@FlowPreview` and `@ExperimentalCoroutinesApi` have historically guarded various Flow operators. - Using such an API triggers a compiler warning/error unless you opt in: annotate the call site with `@OptIn(FlowPreview::class)` (or the relevant marker), or set the `-opt-in` compiler flag. - Experimental signatures may change between kotlinx-coroutines releases, so pin and verify your version. ```kotlin @OptIn(kotlinx.coroutines.ExperimentalCoroutinesApi::class) fun pipeline(f: Flow<Int>) = f.flatMapLatest { fetch(it) } ``` ## Practical guidance - Don't memorise a fixed 'this is experimental' list; the status changes across versions. **Check the version you ship.** - If the compiler complains, add the requested `@OptIn` rather than suppressing all warnings. - Prefer the documented stable operator for the semantics you need (concat/merge/latest) and let map+flatten be an implementation detail you can fall back to. ## Key terms - **Opt-in annotation**: a marker (e.g. `@ExperimentalCoroutinesApi`) requiring explicit `@OptIn` acknowledgement to use an unstable API. - **@FlowPreview / @ExperimentalCoroutinesApi**: kotlinx-coroutines stability markers. - **flattenConcat / flattenMerge**: flatten an existing Flow<Flow<T>>.

  • Is there a flattenLatest operator?
    No. 'Latest' semantics are only available through flatMapLatest / mapLatest / transformLatest.
  • How do you satisfy the compiler when calling an experimental Flow operator?
    Add @OptIn(<MarkerAnnotation>::class) at the call site/function, or set the -opt-in compiler argument; don't blanket-suppress warnings.

saying these in an interview costs you the question

  • Asserting a fixed experimental status without naming the version
  • Inventing a flattenLatest operator
  • Suppressing all warnings instead of targeted @OptIn
  • Thinking flatMapConcat is fundamentally different from map+flattenConcat
  • Confusing flattenMerge's concurrency default with something other than 16

context