skip to content

Explain combine's seeding and conflation behavior: when does it first emit, and can intermediate values be skipped?

level: middleimportance: should knowfreq 45%

answer

  1. First emit only after every source seeded
  2. Each source conflated to its latest value
  3. Slow collector can skip intermediate values
  4. Correct latest-state, not lossless crossings
  5. combineTransform for zero/many emits per trigger

basics

~10 s

combine emits its first value only after every source has produced one. If a source emits faster than the collector consumes, combine may skip older values and use only the most recent of each.

solid answer

~40 s

combine has two subtleties. First, seeding: it produces no output until each input flow has emitted at least once; if any source emits nothing and completes, combine emits nothing. Second, conflation: combine caches the latest value of every source and recombines on each new emission. Because each source is conflated to its newest value, if the downstream collector is slow, intermediate values of a fast source can be dropped — you always recombine with the latest, never a stale queued value. This means combine is not lossless: you get correct latest-state but not every (A,B) crossing. The vararg combine(vararg flows) { array } and combineTransform { } (which may emit zero or many per trigger) share this behavior. For per-trigger control or zero/multi emission use combineTransform.

code

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

// Seed a source that may not emit yet so combine can produce a first value
val name = userNameFlow.onStart { emit("") }
val prefs = prefsFlow.onStart { emit(Prefs.DEFAULT) }

val uiState = name.combine(prefs) { n, p -> UiState(n, p) }
// uiState emits as soon as both have a (possibly default) value,
// then re-emits with the latest of each on any change.

go deeper

for a junior

Knows combine waits for all sources before its first emission.

for a middle

Explains both seeding and conflation: latest-of-each caching means a slow collector can skip intermediate values.

for a senior

Uses onStart/StateFlow to seed missing sources and reaches for combineTransform to inject loading/intermediate states.

for a principal

Reasons about losslessness guarantees, when conflation is correct (derived state) vs. dangerous (event accounting), and chooses the right primitive accordingly.

## Two behaviors to understand `combine` keeps a **cache of the latest value of each source** and re-runs your transform whenever **any** source emits. Two consequences follow. ## 1. Seeding (first emission) `combine` emits **nothing** until **every** source has emitted at least once. The first output uses the first value of each source. ```kotlin val a = flowOf(1, 2) val b = emptyFlow<String>() // never emits a.combine(b) { x, y -> "$x$y" } .collect(::println) // prints nothing: b never seeded ``` If you need a value before a source has emitted, give that source an initial value with `onStart { emit(default) }` or use a `StateFlow` (which always has a current value). ## 2. Conflation of the latest value Each source is **conflated** to its most recent value. If a source produces values faster than the collector consumes the combined results, **older intermediate values can be skipped** — `combine` always recombines with the **newest** cached value, never a stale queued one. ```kotlin // If `fast` bursts 1,2,3 while the collector is busy, // combine may recombine only with 3, skipping 1 and 2. fast.combine(slow) { f, s -> f to s } ``` So `combine` gives you **correct latest state**, but it is **not lossless** — you are not guaranteed every (A, B) crossing. This is usually exactly what you want for derived UI/state. ## 3. `combineTransform` for fine control `combineTransform(a, b) { x, y -> ... }` runs in a `FlowCollector`, so it can `emit` **zero, one, or many** values per trigger and run suspending logic between emits — useful for inserting loading states: ```kotlin combineTransform(user, settings) { u, s -> emit(Ui.Loading) emit(Ui.Ready(u, s)) } ``` ## Keywords/APIs `Flow.combine`, top-level `combine(vararg)`, `combineTransform`, `onStart`, `StateFlow`, `emptyFlow`, conflation. Contrast with `zip`, which is index-paired and therefore does not conflate this way.

  • How do you make combine emit before one of its sources has produced a value?
    Seed that source with onStart { emit(default) }, or use a StateFlow which always carries a current value so it's effectively pre-seeded.
  • Why might combine 'miss' some values from a bursty source?
    Each source is conflated to its latest value; if the collector is slow, intermediate values are overwritten and only the newest is used when recombining.

combine is a scoreboard showing each team's current score; if a team scores twice before you glance up, you only ever see the latest total.

saying these in an interview costs you the question

  • Claiming combine emits a value before all sources have one
  • Asserting combine never drops any source emissions (it's lossless)
  • Confusing conflation with buffering every value
  • Not knowing onStart/StateFlow can seed a missing source
  • Thinking combineTransform can only emit exactly one value

context