skip to content

Compare first(), first { predicate }, and single() as terminal operators. What does each do with the upstream flow, and when does each throw?

level: middleimportance: should knowfreq 60%

answer

  1. first = take one + cancel upstream (short-circuit)
  2. single = drain + demand exactly one
  3. empty -> NoSuchElementException for both
  4. single + 2 items -> IllegalArgumentException
  5. OrNull variants return null instead of throwing

basics

~10 s

first() returns the first value and stops the flow. first { } returns the first matching value. single() expects exactly one value and throws if there are zero or more than one.

solid answer

~40 s

All three are suspending terminal operators. first() collects until the first element, then cancels the upstream via a CancellationException and returns that element; on an empty flow it throws NoSuchElementException. first { predicate } does the same but keeps going until an element satisfies the predicate, cancelling once it does. single() collects the ENTIRE flow expecting exactly one element: it throws NoSuchElementException if empty and IllegalArgumentException ('Flow has more than one element') if a second arrives — so it cannot short-circuit. There are also firstOrNull() and singleOrNull() variants that return null instead of throwing on the empty (and, for single, ambiguous) cases. Use first for 'give me one and stop'; use single to assert a stream truly has one element.

code

kotlin · 9 lines
kotlin
suspend fun demo() {
    val infinite = flow { var i = 0; while (true) emit(i++) }
    println(infinite.first { it == 5 }) // 5, then upstream cancelled -- safe
    // infinite.single() would never return -- avoid

    val one = flowOf("only")
    println(one.single())              // "only"
    println(flowOf("a","b").singleOrNull()) // null (ambiguous)
}

go deeper

for a junior

Knows first() returns the first element and single() expects one element.

for a middle

Distinguishes short-circuit (first) vs full drain (single) and names the two exception types plus the OrNull variants.

for a senior

Explains the cancellation mechanism behind first() and why it is safe on infinite flows whereas single() is not.

for a principal

Reasons about when to assert uniqueness as an invariant vs accept first match, and the failure-mode/observability tradeoffs of throwing vs *OrNull in production code.

## The three operators These are all **suspend** terminal operators that reduce a `Flow<T>` to a single `T`. ### first() Collects until the **first** element arrives, then **cancels** the upstream flow (internally it throws an internal `AbortFlowException`, a `CancellationException` subtype, to stop the producer) and returns that element. - Empty flow -> throws `NoSuchElementException`. - It **short-circuits**: the producer is stopped as soon as one value is seen. ### first { predicate } Like `first()`, but it keeps collecting, testing each element against `predicate`, and returns + cancels on the **first match**. - No element matches (or flow empty) -> `NoSuchElementException`. ### single() Collects the **entire** flow because it must verify there is **exactly one** element. - Empty -> `NoSuchElementException`. - Two or more -> `IllegalArgumentException("Flow has more than one element")`. - Therefore `single()` **cannot short-circuit** — it always drains the source (or fails on the second item). ## The *OrNull variants - `firstOrNull()` / `firstOrNull { predicate }` -> returns `null` instead of throwing when nothing is found. - `singleOrNull()` -> returns `null` when empty **or** when more than one element appears (it does not throw on the ambiguous case; it returns null). ## Code ```kotlin val nums = flowOf(1, 2, 3) nums.first() // 1 (cancels after 1) nums.first { it > 1 } // 2 emptyFlow<Int>().firstOrNull() // null flowOf(42).single() // 42 nums.single() // throws IllegalArgumentException: more than one emptyFlow<Int>().single() // throws NoSuchElementException flowOf(1, 2).singleOrNull() // null (ambiguous) ``` ## Cancellation nuance Because `first` cancels the upstream, any infinite or long-running producer is stopped immediately after the first (matching) emission — making `first` safe on infinite flows, while `single()` on an infinite flow would hang or fail. ## When to use which - **first** — 'I just need one value, stop early' (efficient, safe on infinite flows). - **single** — 'I assert this stream yields exactly one value' (validation / invariant check). - ***OrNull** — same intent but absence/ambiguity is expected, not exceptional.

  • Why is single() unsafe on an infinite flow but first() is fine?
    first() cancels upstream after one element, so infinite producers stop. single() must keep collecting to prove uniqueness, so it never terminates (or throws on the second item).
  • What does singleOrNull() return when the flow has two elements?
    null. Unlike single(), it treats the ambiguous case as absence rather than throwing.

first() grabs the first apple and walks away; single() insists on counting every apple to prove there's only one.

saying these in an interview costs you the question

  • Saying single() short-circuits like first()
  • Thinking first() on an empty flow returns null
  • Confusing which exception single() throws for >1 vs 0 elements
  • Using single() on an infinite/long flow
  • Assuming firstOrNull only handles the empty case for a predicate (it also covers no-match)

context