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?
answer
- Cold = re-run per collect, no caching
- Iterable/List re-iterates fine; one-shot Sequence exhausts
- Side effects run once per collection
- No memoization in flowOf/asFlow by design
- Need sharing/replay -> shareIn/stateIn (hot)
basics
~10 sBecause 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 sflowOf 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 linesimport 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
Knows collecting twice emits the values twice for a list-backed flow.
Distinguishes re-iterable sources from one-shot iterators and that there's no caching.
Reasons about per-collection side effects and chooses shareIn/stateIn when sharing/replay is required.
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