skip to content

Explain the (() -> T).asFlow() and (suspend () -> T).asFlow() overloads. How do they differ from flow { emit(...) } and from flowOf(producer())?

level: seniorimportance: should knowfreq 25%

answer

  1. Function overload: call per collection, emit one value
  2. suspend () -> T overload allows suspending producers
  3. flowOf(producer()) evaluates eagerly at construction
  4. asFlow function overload ≈ flow { emit(producer()) }
  5. Defer + re-evaluate vs precompute + share

basics

~20 s

These overloads take a function (optionally suspending) and make a flow that calls it once per collection, emitting its single return value. flowOf(producer()) instead calls producer eagerly once, before collection, and reuses that fixed result.

solid answer

~40 s

(() -> T).asFlow() and (suspend () -> T).asFlow() wrap a function into a cold flow that, on each collection, invokes the function and emits its single result, then completes. The suspend overload lets the producer call suspend functions. The crucial contrast is evaluation timing: flowOf(producer()) evaluates producer() eagerly at flow-construction time and bakes the value into the cold flow, so every collection emits that same precomputed value. The function overload defers and re-evaluates per collection — important for fresh values, side effects, or suspendable work. Equivalent to flow { emit(producer()) }, but more concise and intent-revealing. Use the function overload when each subscriber should trigger fresh computation (e.g., a fresh timestamp, a retryable network call); use flowOf(value) when the value is already computed and should be shared.

code

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

fun main() = runBlocking {
    var n = 0
    val producer = { n++ }

    val eager = flowOf(producer())   // evaluates once -> 0 baked in
    val deferred = producer.asFlow() // re-invokes each collect

    eager.collect { print("e$it ") }    // e0
    eager.collect { print("e$it ") }    // e0  (same)
    deferred.collect { print("d$it ") }  // d1
    deferred.collect { print("d$it ") }  // d2  (fresh)
}

go deeper

for a junior

Recognizes the function overload makes a flow from a lambda emitting one value.

for a middle

Knows the suspend overload exists and that it re-invokes per collection.

for a senior

Articulates the eager-vs-deferred evaluation contrast between flowOf(producer()) and producer.asFlow() and picks correctly.

for a principal

Connects evaluation timing to subscription semantics, side-effect correctness, and API design intent across builders.

## The function overloads `kotlinx.coroutines.flow` provides: ```kotlin fun <T> (() -> T).asFlow(): Flow<T> fun <T> (suspend () -> T).asFlow(): Flow<T> ``` Each wraps a producer function into a **cold** flow. On every `collect`, the function is invoked and its **single return value** is emitted, then the flow completes. The `suspend` overload allows the producer to call suspending functions. ```kotlin val producer: suspend () -> Int = { fetchCount() } // suspending producer.asFlow().collect { println(it) } // calls fetchCount(), emits result ``` ## Evaluation timing — the key distinction Consider three ways to emit a computed value: ```kotlin // (A) eager: producer() runs NOW, value is fixed val a = flowOf(producer()) // (B) function overload: producer runs on each collect val b = producer.asFlow() // (C) flow builder: same deferral as B val c = flow { emit(producer()) } ``` - In **(A)** `producer()` is evaluated **eagerly** at construction time. The result is captured; every collection re-emits that same precomputed value. (Also: if `producer` is suspending, you can't even write this outside a coroutine.) - In **(B)** and **(C)** evaluation is **deferred** and **repeated per collection**. Each subscriber triggers a fresh invocation. ```kotlin var counter = 0 val producer = { counter++ } val flowB = producer.asFlow() flowB.collect { print(it) } // 0 flowB.collect { print(it) } // 1 (re-invoked) ``` ## When to use which - **Function overload / flow { emit(producer()) }** — when each collection should run fresh work: a new timestamp, a network call you want to retry, or side-effecting computation. - **flowOf(value)** — when the value is already computed and you simply want to stream that constant. ## Relationship to flow { } `producer.asFlow()` is essentially sugar for `flow { emit(producer()) }`. The asFlow overload is shorter and signals 'adapt this producer function'; the explicit builder is needed when you emit multiple derived values or add logic.

  • Why might flowOf(fetch()) be a bug when fetch() is a network call?
    fetch() runs eagerly at construction, off the collector's coroutine timing, and the same result is reused for every collector — losing per-subscription freshness and retry semantics.
  • Can you pass a suspending lambda to flowOf directly?
    No — flowOf takes already-evaluated values. To defer a suspend producer, use (suspend () -> T).asFlow() or flow { emit(producer()) }.

flowOf(producer()) is a photo taken once; producer.asFlow() is a live camera that re-shoots every time someone looks.

saying these in an interview costs you the question

  • Saying flowOf(producer()) defers evaluation until collect
  • Claiming the function overload emits multiple values
  • Not recognizing per-collection re-invocation
  • Thinking flowOf can take a suspend lambda
  • Confusing eager argument evaluation with cold flow laziness

context