When would you reach for the flow { } builder instead of flowOf(...) or a collection's .asFlow(), and what can flow { } do that they cannot?
answer
- flowOf/asFlow = static, already-known values
- flow { } = suspend block, loops, dynamic emission
- All three cold + lazy
- Paging/polling/network => flow { }
- Concurrent/callback => channelFlow/callbackFlow
basics
~10 sUse flow { } when you need to compute or fetch values lazily, call suspend functions, or emit based on logic/loops. flowOf and asFlow just wrap values you already have.
solid answer
~40 sflowOf(a, b, c) and someList.asFlow() are convenience builders for values you already hold in memory — they just emit a fixed, known sequence. The flow { } builder is the general-purpose one: its block is a suspend FlowCollector, so inside it you can call suspend functions (network/db/delay), run loops and conditionals, maintain producer-local state, and decide dynamically what and when to emit. All three are cold and lazy. Reach for flow { } whenever emission requires computation, suspension, side effects, or unknown/unbounded length (e.g. polling, paging). Use flowOf/asFlow for simple, static data to keep code concise. For concurrent or callback-driven emission, neither fits — use channelFlow/callbackFlow.
go deeper
Can pick flowOf for a fixed list and flow { } when 'doing work', even if fuzzy on why.
Clearly maps each builder to its use case and knows flow { }'s suspend superpower.
Discusses cold semantics across all three and when to escalate to channelFlow/callbackFlow.
Considers API ergonomics/readability trade-offs and laziness implications when designing flow-returning functions for a codebase.
## The three cold builders - **`flowOf(vararg values)`** — emits a fixed, comma-separated list: `flowOf(1, 2, 3)`. - **`Iterable/Sequence/Array.asFlow()`** — turns an existing collection into a flow: `listOf(1,2,3).asFlow()`. - **`flow { }`** — the general builder; its block is `suspend FlowCollector<T>.() -> Unit`, so you can do arbitrary suspending logic and call `emit`. All three are **cold** (re-run per collector) and **lazy** (nothing until a terminal operator). ## What only flow { } can do Because the `flow { }` block is `suspend`, it can: - Call other `suspend` functions between emissions (`delay`, HTTP, DB). - Use loops, `if`, `while(true)`, recursion to decide what to emit. - Keep mutable producer-local state across emissions. - Produce an unknown or unbounded number of values (polling, paging, streaming). ```kotlin // Paging: only flow { } fits — it suspends and loops dynamically fun pagedUsers(): Flow<User> = flow { var page = 0 while (true) { val batch = api.fetchPage(page) // suspend if (batch.isEmpty()) break batch.forEach { emit(it) } page++ } } ``` `flowOf`/`asFlow` cannot call suspend functions to *produce* values; they only stream what is already materialized. ## When to pick which - Static, in-memory values → `flowOf` / `asFlow` (concise, clear intent). - Computed/suspended/looped/unbounded → `flow { }`. - Concurrent or callback-based emission → `channelFlow` / `callbackFlow` (not covered by these cold builders). ## Subtlety `emptyFlow()` is a special cold flow that completes immediately with no values — handy as a default instead of `flow { }` that emits nothing.
- Can you call a suspend function inside flowOf(...) to compute one of its arguments?Only if the surrounding code is already in a suspend context, and even then the value is computed once before flowOf runs — not lazily per collector. To compute lazily per collection you need flow { } so the suspend call happens inside the producer block.
flowOf is reading a fixed shopping list aloud; flow { } is deciding each item on the spot while you walk the aisles.
saying these in an interview costs you the question
- Thinks flowOf is lazy about computing its arguments
- Uses flow { } for trivial static lists, adding noise
- Believes asFlow can call suspend functions to generate items
- Doesn't know channelFlow/callbackFlow exist for callbacks