Flow & Channels
A Flow is a cold asynchronous stream that emits values over time and only runs when collected, while a Channel is a hot queue passing values between coroutines.
Learn Flow & Channels in our free Kotlin course — a beginner-friendly interactive lesson with worked examples, a practice exercise and a quick reference.
Part of the free Kotlin course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.
You'll learn the flow {' '} builder, why flows are cold, collecting values, operators like map / filter , how flows differ from sequences, and the basics of Channel .
What You'll Learn in This Lesson
1️⃣ Cold Flows: flow {' '}
A Flow is an asynchronous stream. You build one with the flow {' '} builder and push values with emit() . It's cold : the builder doesn't run until a terminal operator like collect consumes it. Each collector restarts it from the beginning.
Notice "before collect" prints first — proof the flow body waited until collect pulled values through it.
2️⃣ Flow Operators: map & filter
Flows share the familiar operators — map , filter , take — but they run lazily and may suspend . Each returns a new flow; nothing happens until a terminal operator like collect runs. flowOf(...) is a quick way to make a flow from fixed values.
A single collector processes values in emission order, so 1, 9, 25 always print in exactly that sequence — the same mental model as a sequence, but able to suspend.
3️⃣ Channels: Hot Queues Between Coroutines
Where a flow is cold, a Channel is hot : it's a coroutine-safe queue. One coroutine send s values, another receive s them, and each value goes to exactly one consumer. Closing the channel lets a for loop drain it cleanly.
The consumer's for loop receives values in send order until the producer calls close() , so the output is fully ordered.
Your turn. Fill in the ___ , then run and compare.
Create a flow, filter and map it, then collect — all with deterministic, ordered output.
📋 Quick Reference — Flow & Channels
Practice quiz
What does it mean that a Flow is 'cold'?
- It runs on a background thread only
- The builder block does not run until a terminal operator collects it
- It can never be collected twice
- It blocks the main thread
Answer: The builder block does not run until a terminal operator collects it. A cold flow does not run until a terminal operator like collect starts consuming it.
Which builder pushes values into a flow?
- send()
- yield()
- push()
- emit()
Answer: emit(). Inside flow { }, you push values with emit().
Which is a terminal operator that starts a flow?
- collect
- map
- filter
- flowOf
Answer: collect. collect is the terminal operator that actually runs the flow and receives each value.
How do intermediate operators like map and filter behave on a flow?
- They run eagerly and block
- They run lazily and return a new flow; nothing happens until collected
- They consume the flow immediately
- They cannot suspend
Answer: They run lazily and return a new flow; nothing happens until collected. Intermediate operators are lazy, return a new flow, and may suspend; collect triggers them.
How does a Flow differ from a Sequence?
- A Flow cannot have operators
- A Sequence is asynchronous
- A Flow is built for coroutines and can suspend; a Sequence is synchronous and cannot
- They are identical
Answer: A Flow is built for coroutines and can suspend; a Sequence is synchronous and cannot. A Flow can suspend at every step; a Sequence is synchronous and cannot suspend.
Why is the output of a single collector deterministic in order?
- Because flows sort their output
- Because one collector consumes emissions sequentially in production order
- Because collect runs in parallel
- Because emit randomizes order
Answer: Because one collector consumes emissions sequentially in production order. A single collector receives emitted values one at a time in the order produced.
How is a Channel best described?
- A cold, repeatable stream
- A synchronous list
- An extension property
- A hot, coroutine-safe queue between coroutines
Answer: A hot, coroutine-safe queue between coroutines. A Channel is a hot stream: a coroutine-safe queue passing values between coroutines.
In a Channel, each sent value is received by...
- Exactly one consumer
- Every consumer simultaneously
- No consumer
- Only the producer
Answer: Exactly one consumer. Each Channel value is received by exactly one consumer.
Why must a producer call channel.close()?
- To free CPU cores
- So a for loop over the channel can end cleanly instead of hanging
- To restart the channel
- To make it cold
Answer: So a for loop over the channel can end cleanly instead of hanging. Closing lets the consumer's for loop drain remaining values and then finish.
Where must collect be called from?
- From any ordinary function
- From the top level only
- From inside a coroutine, such as runBlocking
- From a constructor
Answer: From inside a coroutine, such as runBlocking. collect is suspending, so it must be called inside a coroutine like runBlocking.