What you'll learn
A suspend function returns a single value. A Flow returns many values over time — asynchronously, one at a time. Flows are Kotlin's reactive streams: think of them as a suspend-friendly, coroutine-native sequence for events, updates, and data that arrives bit by bit.
By the end you'll be able to:
- Explain what a cold flow is
- Build flows with
flow{},flowOf, andasFlow - Transform and collect flows with operators
- Use
StateFlowandSharedFlowfor hot streams
Cold streams
A flow is cold: the code inside flow { ... } doesn't run until a terminal operator like collect subscribes. Each new collector gets its own fresh run. Values are pushed with emit and received in collect:
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
// A Flow is a COLD stream: nothing runs until someone collects it.
fun countdown(): Flow<Int> = flow {
for (i in 3 downTo 1) {
delay(300L)
emit(i) // push a value downstream
}
emit(0)
}
fun main() = runBlocking {
countdown().collect { value -> // collect() starts the flow
println("tick $value")
}
}Key idea
delay, hit the network, or read a database between emissions, all without blocking a thread.Building flows
There are three everyday ways to create a flow:
| Builder | Use it when |
|---|---|
flow { emit(x) } | you compute/emit values yourself, possibly suspending between them |
flowOf(a, b, c) | you already have a fixed set of values |
list.asFlow() | you want to turn an existing collection or range into a flow |
Operators & collecting
Flows carry the same declarative operators you learned for collections — map, filter, take, onEach — but they're intermediate: lazy, and only run when a terminal operator (collect, toList, first) drives the stream:
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
fun main() = runBlocking {
(1..5).asFlow()
.filter { it % 2 == 1 } // keep odd numbers
.map { it * it } // square them
.collect { println(it) } // terminal operator
}Note
buffer() or flowOn(dispatcher) to move the upstream work elsewhere.StateFlow & SharedFlow
Sometimes you want a hot stream that exists independently of collectors. StateFlow always holds a single current value (perfect for UI state), and SharedFlow broadcasts events to multiple collectors. New subscribers to a StateFlow immediately receive the latest value:
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
fun main() = runBlocking {
val state = MutableStateFlow(0) // HOT: always holds a current value
val watcher = launch {
state.collect { println("state = $it") }
}
delay(50L); state.value = 1
delay(50L); state.value = 2
delay(50L); watcher.cancel() // a hot flow is collected until cancelled
}Tip
StateFlow is the modern replacement for LiveData in Android — it drives Jetpack Compose UIs beautifully. Expose a read-only StateFlow publicly and keep the MutableStateFlow private.Combining flows
Flows compose. zip pairs values one-for-one, combine re-emits whenevereither source changes, and flatMapConcat/flatMapMerge flatten a flow of flows:
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
fun main() = runBlocking {
val nums = flowOf(1, 2, 3)
val letters = flowOf("A", "B", "C")
nums.zip(letters) { n, l -> "$n$l" } // pair them up
.collect { println(it) }
}Recap & quick check
Key takeaways
- A Flow emits many values over time asynchronously — a coroutine-native reactive stream.
- Cold flows don't run until collected, and each collector triggers a fresh run.
- Build flows with flow{}, flowOf(...), or collection.asFlow(); push values with emit.
- Intermediate operators (map, filter) are lazy; terminal operators (collect, toList) drive the stream.
- StateFlow holds a current value and SharedFlow broadcasts events — both are hot, ideal for UI state.
Quick check
1. What does it mean that a Flow is 'cold'?
2. How do you emit a value from inside flow { }?
3. Which is a terminal operator?
4. What is StateFlow best suited for?
5. How does a flow handle a slow collector by default?
Flows model streams elegantly. For lower-level, imperative communication between coroutines — queues, producers, and consumers — we turn next to channels and shared state.