skip to content

You wrap a side-effecting iterator-backed source with asFlow() and collect it twice — what happens, and how do these convenience builders preserve cold semantics?

level: seniorimportance: nice to knowfreq 18%

answer

  1. Cold = re-run per collect, no caching
  2. Iterable/List re-iterates fine; one-shot Sequence exhausts
  3. Side effects run once per collection
  4. No memoization in flowOf/asFlow by design
  5. Need sharing/replay -> shareIn/stateIn (hot)

basics

~10 s

Because the flow is cold, collecting twice re-runs the source twice. If the source is a one-shot iterator already consumed, the second collect emits nothing. flowOf and asFlow keep this lazy, replay-per-collect behavior.

solid answer

~40 s

flowOf and asFlow build cold flows: the emission logic runs anew on each collect(), with no shared state or caching between collectors. For an Iterable or a repeatable Sequence (sequence{} / generateSequence), each collection re-iterates from the start, so two collects emit the full sequence twice. But a Sequence backed by a single-use iterator (e.g., one obtained from an InputStream or an already-consumed iterator()) is exhausted after the first pass; the second collect emits nothing. This is the cold contract — builders don't memoize. If you need replay or sharing across collectors, convert to hot with shareIn/stateIn or buffer with a SharedFlow; cold builders intentionally don't do that. Side effects inside the source therefore execute once per collection, which matters for logging, counters, or resource access.

code

kotlin · 8 lines
kotlin
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.runBlocking

fun main() = runBlocking {
    val oneShot = listOf("a", "b").iterator().asSequence().asFlow()
    print("first: "); oneShot.collect { print(it) }; println()
    print("second: "); oneShot.collect { print(it) }; println() // empty
}

go deeper

for a junior

Knows collecting twice emits the values twice for a list-backed flow.

for a middle

Distinguishes re-iterable sources from one-shot iterators and that there's no caching.

for a senior

Reasons about per-collection side effects and chooses shareIn/stateIn when sharing/replay is required.

for a principal

Frames the cold contract as an intentional design boundary and architects hot/cold conversions around resource and subscription semantics.

## Cold = re-run per collect Both `flowOf` and `asFlow` produce **cold** flows. 'Cold' means: the producer body runs **from scratch every time** a terminal operator collects, and there is **no shared state or caching** between separate collectors. ```kotlin val f = listOf(1, 2, 3).asFlow() f.collect { print(it) } // 123 f.collect { print(it) } // 123 again — re-iterated ``` ## Repeatable vs one-shot sources The replay behavior depends on whether the **underlying source** can be re-iterated: - **Iterable (List, Set):** `iterator()` returns a fresh iterator each call, so re-iteration works. Two collects → values twice. - **Repeatable Sequence** (`sequence { }`, `generateSequence`): produces a fresh iterator each iteration, so it replays. - **One-shot Sequence** (wrapping an already-consumed iterator, e.g. `iterator.asSequence()` or a stream-backed sequence): once exhausted, a second collect emits **nothing**. ```kotlin val oneShot = listOf(1, 2, 3).iterator().asSequence() val flow = oneShot.asFlow() flow.collect { print(it) } // 123 flow.collect { print(it) } // (nothing) — iterator exhausted ``` ## Side effects run per collection Because the body re-runs, any **side effect** in the source executes once per collection: ```kotlin var reads = 0 val src = sequence { reads++; yield(1); yield(2) } val f = src.asFlow() f.collect { } f.collect { } // reads == 2 — source block ran twice ``` This matters for logging, counters, network/file access, and resource lifetimes. ## When you need replay/sharing Cold builders deliberately do **not** memoize. To share emissions across collectors or replay buffered values, convert to a **hot** flow: - `flow.shareIn(scope, started, replay)` → `SharedFlow` - `flow.stateIn(scope, started, initialValue)` → `StateFlow` These add a shared upstream subscription and a replay cache, which cold `flowOf`/`asFlow` intentionally lack. ## Summary The convenience builders preserve cold semantics by re-executing emission per collect with no caching; correctness around side effects and one-shot sources depends entirely on the re-iterability of the wrapped source.

  • How would you let multiple collectors share one upstream emission instead of re-running it?
    Convert the cold flow to hot with shareIn (SharedFlow) or stateIn (StateFlow), giving a shared subscription and optional replay cache.
  • Why does a List-backed asFlow replay but a stream-backed Sequence may not?
    List.iterator() returns a fresh iterator each collection; a stream-backed/one-shot Sequence iterator is consumed once and cannot restart.

saying these in an interview costs you the question

  • Assuming asFlow caches the first iteration's results
  • Expecting a one-shot Sequence to replay on a second collect
  • Thinking side effects in the source run only once total
  • Using cold builders and expecting hot-style sharing
  • Believing flowOf memoizes across collectors

context