skip to content

What does merge do with multiple Flows, and how does it differ from zip and combine?

level: middleimportance: should knowfreq 55%

answer

  1. merge = concurrent fan-in, no transform
  2. Items pass through unchanged, interleaved by arrival
  3. All sources must share the element type
  4. Completes only when every source completes
  5. flattenMerge for a Flow<Flow<T>> with concurrency limit

basics

~10 s

merge runs several flows at once and forwards each item as it arrives into one flow. It does not pair or transform values — it just interleaves them in arrival order.

solid answer

~40 s

merge takes multiple Flows of the same type and collects them concurrently, re-emitting every element from every source into a single flow as soon as it appears. Unlike zip and combine, merge does not join values together: there is no transform lambda and no pairing — each upstream item passes through unchanged. The output order is the interleaving of arrival times across sources, so it is non-deterministic when sources race. The merged flow completes only after all sources complete; if any source throws, the exception propagates and the others are cancelled. It is exposed as a top-level function merge(vararg flows) and as Iterable<Flow<T>>.merge() / Flow<Flow<T>>.flattenMerge(). Use merge to fan in homogeneous event streams (e.g. several sensors) into one pipeline.

code

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

val a = flowOf(1, 2)
val b = flowOf(10, 20)
// No lambda: values are forwarded untouched, interleaved by arrival
merge(a, b).collect(::println)   // e.g. 1, 10, 2, 20

// Collection form
listOf(a, b).merge().collect(::println)

go deeper

for a junior

Knows merge interleaves items from several flows into one without transforming them.

for a middle

Explains concurrent collection, same-type requirement, arrival-order interleaving, and completion-after-all semantics.

for a senior

Contrasts merge with zip/combine on the value-correlation axis and reaches for flattenMerge with a concurrency limit when flattening a flow-of-flows.

for a principal

Reasons about structured-concurrency error propagation, non-determinism implications for testing, and bounding fan-in concurrency under load.

## What `merge` is `merge` is the **fan-in** combiner. Given several `Flow<T>` of the **same element type**, it returns a single `Flow<T>` that collects all of them **concurrently** and forwards each emitted item the instant it arrives. There is **no transform lambda** and **no pairing**: values are passed through untouched and simply **interleaved**. ```kotlin import kotlinx.coroutines.flow.merge import kotlinx.coroutines.flow.flowOf val clicks = flowOf("click") val keys = flowOf("key-A", "key-B") merge(clicks, keys).collect(::println) // prints click, key-A, key-B in arrival order (timing-dependent) ``` ## Forms of the API - `merge(vararg flows: Flow<T>): Flow<T>` — top-level. - `Iterable<Flow<T>>.merge(): Flow<T>` — extension on a collection of flows. - `Flow<Flow<T>>.flattenMerge(concurrency = N)` — flattens a flow-of-flows, with a configurable concurrency limit (`merge` is essentially `flattenMerge` with default concurrency). ## Semantics that matter - **Concurrency:** all sources are collected at the same time, each in its own child coroutine of the collecting scope. - **Ordering:** output reflects **arrival order**; with racing sources it is **non-deterministic**. - **Completion:** the merged flow completes only after **every** source completes. - **Errors / cancellation:** an exception in any source propagates to the collector and **cancels the siblings** (structured concurrency). ## How it differs from `zip` / `combine` | Operator | Joins values? | Output type | Triggered by | |---|---|---|---| | `zip` | yes (lockstep pairs) | transformed `R` | both sides ready for index *n* | | `combine` | yes (latest-of-each) | transformed `R` | any source, after all seeded | | `merge` | **no** | same `T`, untouched | any source, immediately | So `zip`/`combine` **correlate** values into a new shape, while `merge` simply **flattens** independent streams of one type into one. ## When to use Fan-in of homogeneous events: multiple WebSocket channels, several sensors, retrying sources, or UI events from different widgets that feed one handler.

  • Can merge combine a Flow<Int> with a Flow<String>?
    Not directly — merge requires the same element type. You would map each to a common type (e.g. a sealed Event) first, or use combine/zip which transform into a new type.
  • Is the output order of merge guaranteed?
    No. It reflects arrival order across concurrently collected sources, so racing emissions interleave non-deterministically.

merge is several rivers flowing into one channel — the water mixes in whatever order it arrives, nothing is paired up.

saying these in an interview costs you the question

  • Claiming merge pairs or transforms values
  • Saying merge preserves a strict ordering across sources
  • Thinking merge can join flows of different element types
  • Believing merge collects sources sequentially rather than concurrently
  • Assuming one source finishing completes the whole merged flow

context