skip to content

Explain take and drop on a Flow. What is special about how take(n) terminates the upstream, and what exception underlies it?

level: middleimportance: should knowfreq 45%

answer

  1. take(n) = first n then cancel upstream
  2. AbortFlowException stops take (caught by take)
  3. takeWhile excludes the failing value
  4. drop(n) skips first n; dropWhile skips leading run
  5. broad catch around emit can break take

basics

~20 s

take(n) lets through only the first n values then stops the flow; drop(n) skips the first n and emits the rest. take cancels the upstream once it has enough by throwing an internal exception that take catches.

solid answer

~40 s

take(n) emits at most the first n upstream values, then cancels collection of the upstream so no further work happens — useful for short-circuiting infinite flows. Internally it stops by throwing AbortFlowException, a private control-flow exception that the take operator itself catches; this is why you must be careful with try/catch inside upstream code. takeWhile(predicate) emits until the predicate first returns false (exclusive). drop(n) discards the first n values and emits the rest; dropWhile(predicate) discards the leading run while the predicate holds, then emits everything from the first failing element onward. These compose with map/filter and respect order. take's cancellation means side-effecting upstream code may not run for skipped tail elements.

code

kotlin · 4 lines
kotlin
// take bounds an infinite flow; placement vs filter matters
val a = (1..100).asFlow().filter { it % 2 == 0 }.take(3).toList() // [2,4,6]
val b = (1..100).asFlow().take(3).filter { it % 2 == 0 }.toList() // [2]
println("$a $b")

go deeper

for a junior

Knows take keeps the first n and drop skips the first n.

for a middle

Explains takeWhile/dropWhile semantics and that take cancels the upstream rather than draining it.

for a senior

Names AbortFlowException, the try/catch pitfall, and placement-vs-filter effects on results.

for a principal

Reasons about resource cleanup under upstream cancellation and exception-transparency guarantees of the catch operator.

## take and takeWhile `take(n)` passes through the first `n` values and then **stops** the flow: ```kotlin (1..Int.MAX_VALUE).asFlow() .take(3) // 1, 2, 3 then done .collect(::println) ``` This is the idiom for bounding an **infinite** or very long flow. After the n-th value, `take` must stop the upstream. It does so by throwing a private `AbortFlowException` from inside the collector; the `take` operator **catches that same exception** and completes normally. So the upstream coroutine is cancelled and the terminal collect returns cleanly. ### Gotcha: try/catch around emit Because `take` relies on an exception, a broad `try { emit(x) } catch (e: Exception) { }` in your **upstream** `flow { }` can swallow `AbortFlowException` and break `take`. Prefer catching specific exceptions, or use the `catch` operator (which is exception-transparent and ignores downstream/abort exceptions). `takeWhile { predicate }` emits values **until** the predicate first returns `false`, and that failing value is **not** emitted: ```kotlin flowOf(1, 2, 3, 1).takeWhile { it < 3 } // 1, 2 ``` ## drop and dropWhile `drop(n)` **skips** the first `n` values and emits the remainder: ```kotlin flowOf(1, 2, 3, 4).drop(2) // 3, 4 ``` `dropWhile { predicate }` skips the leading run while the predicate is `true`, then emits **everything** from the first failing element onward (it does not re-evaluate later): ```kotlin flowOf(1, 2, 3, 1).dropWhile { it < 3 } // 3, 1 ``` ## Ordering and composition All four preserve order and compose with `map`/`filter`. Where you place `take` matters: `filter { }.take(3)` takes 3 *survivors*, while `take(3).filter { }` takes 3 *inputs* then filters them. ## Cancellation semantics Because `take` cancels upstream once satisfied, upstream resources opened via `flow { }` should be released in a `finally`/`onCompletion`, since the producer is cancelled rather than allowed to finish naturally.

  • Why can a try/catch in your flow builder break take(n)?
    take stops upstream by throwing AbortFlowException; a broad catch (Exception) around emit can swallow it, so the upstream keeps running and take never completes correctly.
  • Does takeWhile emit the element that fails the predicate?
    No. takeWhile is exclusive — the first element failing the predicate is dropped and the flow completes.

saying these in an interview costs you the question

  • Saying take(n) just filters but lets upstream run to completion
  • Thinking takeWhile includes the failing element
  • Confusing dropWhile with filter (dropWhile only skips the leading run)
  • Using catch-all try/catch around emit and breaking take
  • Believing operator placement relative to filter doesn't matter

context