What does it mean that Flow is "cold," and what runs the flow's producer block? Show what happens if no one collects.
answer
- Cold = lazy: no collector, no work
- Terminal operator (collect/toList/first) starts it
- Producer re-runs fresh for each collector
- Producer runs in the COLLECTOR's coroutine/context
- Building/operators = recipe; collect = execution
basics
~10 sCold means the flow does nothing until someone collects it. The producer code only runs when collect is called, and it runs fresh for each collector. No collector, no work.
solid answer
~40 sA Flow is cold: defining it with flow { ... } or flowOf(...) does no work. The producer block executes lazily and only when a terminal operator such as collect, toList, or first is called. Each new collection re-runs the producer from scratch in the collector's coroutine, so two collectors get two independent executions. This mirrors a function call: declaring a function runs nothing; calling it does. Because the producer runs inside the collector's coroutine context, it inherits that scope's cancellation and dispatcher. Contrast this with hot streams (StateFlow/SharedFlow) which are active and emit regardless of collectors — but those are a separate concern. The cold model is why side effects belong inside the flow builder, not before it.
code
kotlin · 9 linesfun events(): Flow<Int> = flow {
println("connecting...") // a side effect that re-runs per collector
emit(1); emit(2)
}
val f = events() // prints nothing yet
f.map { it * 10 } // still nothing
f.collect { println(it) } // NOW: connecting... 1 2 (after map: 10 20)go deeper
Knows nothing happens until you collect and that collect triggers the work.
Explains cold means lazy + per-collector re-execution, and lists terminal operators that start it.
Adds context preservation (producer runs in collector's context), flowOn for dispatcher changes, and why side effects belong inside the builder.
Reasons about implications: idempotency/retry semantics, when to convert cold→hot via shareIn/stateIn, and cost of re-running expensive producers per collector.
## Cold = nothing happens until collected A **cold** stream is one whose producer code is **inert until a terminal operator subscribes**. Building a `Flow` — `flow { ... }`, `flowOf(...)`, `asFlow()` — and chaining operators like `map`/`filter` builds a *recipe*; it does **not** run anything. The work starts only when a **terminal operator** is invoked: - `collect { }` — the canonical consumer - `toList()` / `toSet()` — collect into a collection - `first()`, `single()`, `reduce()`, `fold()`, `count()` - `launchIn(scope)` — collect in a separate coroutine ## "Runs fresh per collector" Each call to a terminal operator **re-executes the producer block from the beginning**, in the **collector's coroutine**. Two `collect` calls trigger two independent runs — like calling the same function twice. ```kotlin val flow = flow { println("producer started") emit(1) emit(2) } // At this point NOTHING has printed. flow.collect { println("A: $it") } // prints "producer started", A:1, A:2 flow.collect { println("B: $it") } // prints "producer started" AGAIN, B:1, B:2 ``` ## Why "cold" matters - **Side effects go inside the builder.** Putting a side effect before/around the flow runs it once at definition time, not per collection. - **Context inheritance:** the producer runs in the **collector's** coroutine context (dispatcher + Job), so cancelling the collector cancels the producer. This is *context preservation*; you change the producer's dispatcher with `flowOn`, not by switching context inside the builder (which throws `IllegalStateException`). - **Restartable / replayable by construction:** because each collection is fresh, retries (`retry`) and re-collection just re-run the recipe. ## Cold vs hot (boundary) `StateFlow` and `SharedFlow` are **hot**: they exist and emit independent of collectors, and multiple collectors share one stream. That is a separate topic; the point here is that the *default* `Flow` is cold and per-collector.
- If you call collect twice on the same cold Flow, how many times does the producer run?Twice — once per terminal collection, each independently from the start.
- How do you change the dispatcher the producer runs on if it inherits the collector's context?Use flowOn(dispatcher) upstream; emitting from a different context inside the builder is illegal and throws.
- What makes StateFlow different from this cold model?StateFlow is hot: it holds state and emits regardless of collectors, and all collectors share the same active stream.
A cold flow is a recipe card: writing it down cooks nothing; only when someone cooks (collects) does food appear — and each cook starts from scratch.
saying these in an interview costs you the question
- Saying the producer block runs as soon as the flow is created
- Believing all collectors share one producer execution for a plain Flow
- Calling withContext inside flow { } to change dispatcher (illegal — use flowOn)
- Thinking operators like map start the flow
- Confusing cold Flow with hot StateFlow/SharedFlow behavior