← Back to Kotlin Course | Chapter 12: Coroutines | Lesson 5 of 7

Flow Basics

A Flow is like a conveyor belt that delivers a whole stream of values to you over time, one at a time, instead of all at once.

Creating a Flow

flow { } builds a cold stream that emits values one at a time using emit(); nothing inside runs until the flow is collected.

Warning: This example uses kotlinx.coroutines, which is not part of the plain Kotlin standard library. It requires the kotlinx-coroutines-core dependency (added via Gradle/Maven) to compile and run.

Example: Creating a Flow

markup
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*

fun numberFlow(): Flow<Int> = flow {
    for (i in 1..3) {
        delay(20)
        emit(i)
    }
}

fun main() = runBlocking {
    numberFlow().collect { value ->
        println("Collected: $value")
    }
}

Flows Are Cold

A Flow does not start producing values until .collect { } (or another terminal operator) is actually called on it, meaning the same flow can be collected multiple times, running its logic fresh each time.

Warning: This example uses kotlinx.coroutines, which is not part of the plain Kotlin standard library. It requires the kotlinx-coroutines-core dependency (added via Gradle/Maven) to compile and run.

Example: Flows Are Cold

markup
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*

fun greetingFlow(): Flow<String> = flow {
    println("Flow started")
    emit("Hello")
}

fun main() = runBlocking {
    println("Before collecting")
    val flow = greetingFlow()
    println("Flow created, but not started yet")
    flow.collect { println(it) }
}

Transforming a Flow

Operators like .map { } and .filter { } work on a Flow much like they do on a collection, building up a pipeline that runs when the flow is finally collected.

Warning: This example uses kotlinx.coroutines, which is not part of the plain Kotlin standard library. It requires the kotlinx-coroutines-core dependency (added via Gradle/Maven) to compile and run.

Example: Transforming a Flow

markup
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*

fun main() = runBlocking {
    (1..5).asFlow()
        .filter { it % 2 == 0 }
        .map { it * 10 }
        .collect { println("Value: $it") }
}

Flow vs a Single Suspend Result

While a plain suspend fun returns exactly one value, a Flow is built for representing zero, one, or many values arriving over time, making it a better fit for streams of ongoing data.

Warning: This example uses kotlinx.coroutines, which is not part of the plain Kotlin standard library. It requires the kotlinx-coroutines-core dependency (added via Gradle/Maven) to compile and run.

Example: Flow vs a Single Suspend Result

markup
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*

fun main() = runBlocking {
    val total = (1..5).asFlow().fold(0) { acc, value -> acc + value }
    println("Total from flow: $total")
}
Common Mistakes
  1. Forgetting that a Flow is cold -- its code doesn't run at all until something actually collects it with .collect { }.
  2. Blocking inside a Flow's collector instead of using suspend-friendly operations, which can defeat the purpose of using a Flow.
  3. Confusing Flow (built for potentially many asynchronous values over time) with a single suspend fun returning one value, or with Sequence (synchronous, not suspend-aware).
Chapter Summary
  • flow { } builds a cold asynchronous stream of values, emitted one at a time with emit().
  • A Flow does nothing until .collect { } is called on it -- it is lazily executed, similar in spirit to a Sequence.
  • Flow operators like .map { } and .filter { } transform the stream, similar to collection operations but suspend-aware.
  • Flows are provided by kotlinx.coroutines and are commonly used for streams of data that arrive over time.
🔒

Chapter Quiz — Complete all 7 topics to unlock

0/7 topics done

Complete these topics first:

Login to run this code

C/C++/Java/PHP execution requires a free account. Your code is saved — you'll land right back in the editor after logging in.