What is the difference between flow.first(), flow.take(n).toList(), and flow.toList() when collecting in a test, and when do you use each?
answer
- first() = one value then cancel; firstOrNull() avoids exception
- take(n).toList() = bounded prefix of n
- toList() = everything, needs completion
- single() = exactly one or throw
- Bound infinite flows so runTest doesn't hang
basics
~20 sfirst() grabs only the very first emission and stops. take(n).toList() collects the first n emissions then stops. toList() collects everything until the flow finishes. Use the bounded ones when the flow may not complete on its own.
solid answer
~40 sAll three are ways to collect, but they differ in how much they consume. flow.first() is a terminal operator that returns the first emitted value and cancels collection immediately—ideal when you only care about the initial value, including from flows that never complete. flow.take(n) is an intermediate operator that re-emits at most n items then completes the upstream via cancellation; pairing it with toList() yields a List of exactly those n (or fewer if upstream ends early). flow.toList() drains the entire flow and only returns once it completes. For finite, self-completing flows, toList() asserts the full sequence with assertEquals(listOf(...), result). For potentially infinite or long flows, bound them with take(n).toList() or first() so runTest doesn't hang. There's also firstOrNull() and single() for stricter expectations.
code
kotlin · 5 lines@Test
fun firstThrowsOnEmpty() = runTest {
assertEquals(0, flowOf(0, 1).first()) // takes 0, cancels rest
assertEquals(null, emptyFlow<Int>().firstOrNull()) // empty -> null, no throw
}go deeper
Can name the three operators and that toList() needs completion.
Explains terminal vs intermediate, cancellation semantics, and picks the right one for finite vs infinite flows.
Adds single()/firstOrNull(), empty-flow exception behavior, and how bounding prevents runTest timeouts.
Sets team guidance on collection strategy per flow shape and ties it to flaky-test prevention.
## The three collection strategies When testing a `Flow<T>`, you choose how much of the stream to consume: ### `flow.toList()` - **Terminal** operator; drains the flow and returns a `List<T>` of *every* emission. - Only returns when the flow **completes**. Best for finite flows. ### `flow.first()` - **Terminal** operator returning the **first** emitted element of type `T`. - As soon as one value arrives, it **cancels** upstream collection and returns. - Works even on infinite flows because it stops after one item. - Throws `NoSuchElementException` if the flow completes with **zero** emissions. Use `firstOrNull()` to get `null` instead. ### `flow.take(n).toList()` - `take(n)` is an **intermediate** operator: it re-emits at most `n` items, then signals completion (cancels upstream). - Chaining `.toList()` collects exactly those items into a `List`. If upstream emits fewer than `n` and completes, you simply get fewer. - Use it to bound a long or infinite flow to a known prefix you can assert. ```kotlin @Test fun boundedCollection() = runTest { val infinite = flow { var i = 0 while (true) { emit(i++) } // never completes on its own } val firstThree = infinite.take(3).toList() assertEquals(listOf(0, 1, 2), firstThree) val justFirst = infinite.first() assertEquals(0, justFirst) } ``` ## Why bounding matters in tests `runTest` will fail with a timeout if a coroutine never finishes. Calling `toList()` on an infinite flow hangs forever. `first()` and `take(n).toList()` cancel the upstream once they have what they need, so the coroutine completes and the test passes. ## Related stricter operators - `single()` — expects **exactly one** emission; throws if zero or more than one. Good for asserting "emits precisely one value". - `firstOrNull()` — first value or `null` if empty (no exception). - `toList(destination)` — collect into a supplied mutable list. ## Asserting For `toList()` / `take(n).toList()` compare lists: `assertEquals(listOf(...), result)`. For `first()` / `single()` compare scalars: `assertEquals(expected, value)`. ## Keywords `first()`, `firstOrNull()`, `single()`, `take(n)`, `toList()`, terminal vs intermediate operator, cancellation, `runTest`.
- What does single() add over first()?single() asserts there is exactly one emission—it throws if the flow emits zero or more than one—so it doubles as a count assertion.
- Does take(2) cancel the upstream after the second item?Yes. Once take reaches its limit it stops collecting and cancels upstream, which is why it can bound an infinite flow.
saying these in an interview costs you the question
- Saying first() collects the whole flow then returns element 0
- Believing take(n).toList() can hang on an infinite flow
- Confusing first() (throws on empty) with firstOrNull()
- Calling toList() on an infinite flow to 'just get a few'
- Thinking single() and first() behave identically