skip to content

How Kotlin Does Reactive Streams

A Flow is a cold asynchronous sequence whose producer and operators are suspend functions, so backpressure falls out of suspension rather than an explicit request protocol. That is the key comparison against callback-based and Reactive Streams libraries.

part ofKotlinoverview, primer and where to startread it →
on this pageshow

questions

5

What is a Kotlin Flow, and how does it relate to a regular list and to reactive streams like RxJava's Observable?

level: juniorimportance: must knowfreq 80%

answer

  1. List = all values now; Flow = values over time
  2. Sequence is lazy+sync; Flow is lazy+async (can suspend)
  3. Flow = Kotlin's Observable/Flux, built on coroutines
  4. emit() pushes, collect() pulls/runs
  5. Suspension instead of callbacks/Schedulers

basics

~10 s

A Flow is a stream of values produced over time, one after another. Unlike a list, the values can arrive asynchronously without blocking. It is Kotlin's coroutine-based answer to reactive streams.

solid answer

~40 s

Flow<T> is an asynchronous data stream that emits zero or more values of type T over time and then completes (or fails). A List<T> holds all values at once in memory; a Flow produces them lazily, possibly asynchronously, using suspend functions so it never blocks a thread. It plays the same role as RxJava's Observable or Reactor's Flux: representing a sequence of events you react to. The key Kotlin difference is that Flow is built on coroutines and structured concurrency instead of callbacks and Schedulers — emission and collection happen inside coroutines, and suspension (not buffering or callbacks) is the primitive used to coordinate producer and consumer speed.

code

kotlin · 8 lines
kotlin
val temps: Flow<Int> = flow {
    while (true) {
        emit(readSensor())  // suspend network/IO call, no blocking
        delay(1000)
    }
}

temps.collect { temp -> println("Now: $temp") }

go deeper

for a junior

Knows Flow is an async stream of values over time, unlike a List, and that collect consumes it.

for a middle

Articulates the Sequence (sync, can't suspend) vs Flow (async, can suspend) distinction and maps Flow onto Observable/Flux.

for a senior

Explains that suspension (not callbacks/Schedulers) is the coordination primitive and ties Flow to structured concurrency.

for a principal

Frames Flow as a design choice: pushing reactive semantics onto suspend/coroutines to unify async code, with implications for tooling, debugging, and interop with Rx/Reactor.

## What a Flow is `Flow<T>` (from `kotlinx.coroutines.flow`) is an **asynchronous sequence**: it produces a stream of values of type `T`, in order, over time, and then either completes normally or completes with an exception. It is the coroutine-native equivalent of a reactive stream. ## Flow vs List vs Sequence - A `List<T>` is **eager and finite**: every element already exists in memory. - A `Sequence<T>` is **lazy but synchronous**: elements are computed on demand, but each step runs on the calling thread and **cannot suspend** (you can't call `suspend` functions inside a sequence's `yield`). - A `Flow<T>` is **lazy and asynchronous**: producing the next element may run a `suspend` function (e.g. a network call) without blocking the thread. ## Flow vs reactive streams (Rx/Reactor) Flow occupies the same niche as RxJava's `Observable`/`Flowable` or Project Reactor's `Flux`: a push-style stream of events you transform with operators (`map`, `filter`, etc.). The conceptual differences: - Flow uses **coroutines and `suspend`** for asynchrony, so there are no callbacks and no `Scheduler` objects. - Backpressure is handled implicitly by **suspension**: a slow collector simply suspends the producer. - Flow is integrated with **structured concurrency**, so cancellation and scope are inherited automatically. ```kotlin import kotlinx.coroutines.flow.* val numbers: Flow<Int> = flow { for (i in 1..3) { emit(i) // push a value to the collector } } // nothing runs until someone collects: numbers.collect { println(it) } // 1, 2, 3 ``` The `flow { ... }` builder is a coroutine; `emit` sends a value downstream; `collect` is a `suspend` terminal operator that runs the stream.

  • Can you build a Flow from a fixed set of values without the flow { } builder?
    Yes — flowOf(1, 2, 3) or listOf(1,2,3).asFlow() create a Flow from known values.
  • Why can't a regular Sequence call a suspend function?
    Sequence's iterator runs synchronously on the calling thread; it has no coroutine context, so suspension points aren't allowed. Flow's builder is a coroutine, so it can suspend.

A List is a delivered package of all items; a Flow is a conveyor belt that hands you items one at a time as they're produced.

saying these in an interview costs you the question

  • Saying a Flow holds all its values in memory like a List
  • Claiming Flow blocks the thread while waiting for the next value
  • Confusing Flow with Sequence and saying both can suspend
  • Thinking Flow uses callbacks/listeners under the hood
  • Saying you must subscribe with a callback object like in RxJava

context

open as a page

What does it mean that Flow is "cold," and what runs the flow's producer block? Show what happens if no one collects.

level: middleimportance: must knowfreq 75%

basics

~10 s

Cold 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.

open as a page

Explain how a Flow integrates with structured concurrency: in whose context does emission run, and how does cancellation propagate?

level: seniorimportance: should knowfreq 50%

basics

~10 s

A flow runs inside the coroutine that collects it. So it uses that coroutine's thread and lifecycle: if the collecting coroutine is cancelled, the flow stops too. Producer and consumer share one structured scope.

open as a page

How does Flow handle backpressure without an explicit request(n) mechanism like Reactive Streams?

level: seniorimportance: should knowfreq 60%

basics

~20 s

When the collector is slow, the producer's emit call simply suspends and waits. No values are dropped or buffered by default — the producer politely pauses until the collector is ready for the next value.

open as a page

Why did Kotlin model Flow as cold and suspend-based rather than adopting the Reactive Streams Publisher/Subscriber callback model? What trade-offs does that create, including interop?

level: principalimportance: nice to knowfreq 30%

basics

~20 s

Building Flow on coroutines lets async stream code read like normal sequential code, with built-in cancellation and backpressure via suspension. The trade-off is that the default Flow lacks multicasting and needs adapters to talk to RxJava/Reactor.

open as a page