skip to content

Operators

The operator vocabulary over Flow: transforming, combining several flows, flattening flows of flows, and the terminal operators that actually start collection. Most Flow interview problems are really operator-selection problems.

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

explore

questions

20

What is the difference between zip and combine when joining two Flows in Kotlin?

level: juniorimportance: must knowfreq 70%

answer

  1. zip = lockstep pairs by index
  2. combine = latest-of-each, fires on any emission
  3. zip stops at the shorter flow, drops the rest
  4. combine needs every source to emit once first
  5. combine emits more often than zip

basics

~10 s

zip waits for one new item from each flow and pairs them in lockstep. combine fires whenever either flow emits, using the newest value from the other flow.

solid answer

~40 s

Both zip and combine merge two (or more) Flows into one. zip is strictly paired: it takes the 1st of A with the 1st of B, the 2nd with the 2nd, and so on, waiting for both sides before emitting. It stops when the shorter flow completes, so leftover items are dropped. combine is latest-wins: after both flows have produced at least one value, every emission from either flow triggers a new combined value using the most recent value of the other. combine emits more often and never discards a 'paired index' — it reacts to whichever source changed. Use zip for in-step pairs (request/response indices), combine for keeping a derived state in sync with the latest inputs (e.g. UI state from several streams).

code

kotlin · 8 lines
kotlin
val nums = flowOf(1, 2, 3)
val letters = flowOf("a", "b")

// zip: lockstep, shorter wins -> 1a, 2b
nums.zip(letters) { n, l -> "$n$l" }.collect(::println)

// combine: latest-of-each -> reacts to whichever changed
nums.combine(letters) { n, l -> "$n$l" }.collect(::println)

go deeper

for a junior

States the core distinction: zip = lockstep pairs, combine = latest-of-each on any emission.

for a middle

Adds that zip stops at the shorter flow and drops extras, while combine needs a first value from each before emitting.

for a senior

Picks the right operator per use case (paired indices vs. latest-state) and notes combine's higher emission count and timing dependence.

for a principal

Frames the choice in terms of correctness of derived state and backpressure/cancellation semantics across cold flows.

## The problem You often have more than one `Flow` and need a single stream that joins their values. Kotlin's `kotlinx.coroutines.flow` package gives three core combiners: `zip`, `combine`, and `merge`. This question contrasts the two value-joining ones. ## `zip` — lockstep pairing by index `zip` pairs elements **positionally**: element 0 of A with element 0 of B, element 1 with element 1, etc. To emit pair *n* it must wait until **both** flows have produced their *n*-th element. The transform you pass produces the combined value. - It completes (and cancels the other) as soon as **either** flow completes — extra trailing elements of the longer flow are **dropped**. - Backpressure is symmetric: a fast flow waits for the slow one. ```kotlin val nums = flowOf(1, 2, 3) val letters = flowOf("a", "b") // shorter nums.zip(letters) { n, l -> "$n$l" } .collect(::println) // 1a, 2b (3 is dropped) ``` ## `combine` — re-emit on the latest of each `combine` waits until **every** source has emitted at least once, then emits an initial combined value. After that, **any** emission from **any** source produces a new combined value using the **most recent** value of each other source. ```kotlin val a = flowOf(1, 2, 3) val b = flowOf("x", "y") a.combine(b) { n, s -> "$n$s" } .collect(::println) // e.g. 1x, 2x, 2y, 3y (exact interleaving is timing-dependent) ``` Notice it does **not** drop items and emits roughly `(emissions of A) + (emissions of B) - 1` times. There is also a vararg form `combine(vararg flows) { array -> ... }` and a `combineTransform { ... }` variant that can emit zero or many values per trigger. ## When to use which - **`zip`**: you genuinely need paired-by-index data — e.g. matching the *i*-th request to the *i*-th response. - **`combine`**: you want a derived value that should reflect the **latest** of several independent inputs — e.g. forming UI state from a username flow and a settings flow. ## Key APIs/keywords `Flow.zip`, `Flow.combine`, top-level `combine(...)`, `combineTransform`, `flowOf`, `collect`. All are cold-flow operators that run inside a coroutine and respect cancellation.

  • If flow A has 5 items and flow B has 2, how many items does zip emit?
    Exactly 2 — zip stops when the shorter flow (B) completes, so the last 3 items of A are dropped.
  • Does combine emit anything before both flows have produced a value?
    No. combine waits for every source to emit at least once before producing its first combined value.

zip is a zipper joining teeth one-for-one; combine is a dashboard that refreshes whenever any single gauge moves.

saying these in an interview costs you the question

  • Saying zip and combine behave identically
  • Claiming zip emits for every item of the longer flow
  • Thinking combine pairs by index like zip
  • Believing combine emits before all sources have a first value
  • Assuming these operators run on a background thread automatically

context

open as a page

What problem do flatMapConcat, flatMapMerge, and flatMapLatest solve in Kotlin Flow, and how do they differ at a high level?

level: juniorimportance: must knowfreq 70%

basics

~20 s

Each emitted value can itself produce a flow. These operators flatten those inner flows into one stream. Concat runs them one after another, Merge runs them at the same time, Latest cancels the old inner flow when a new value comes.

open as a page

What is a terminal operator on a Kotlin Flow, and why must you call one for any code to run? Give two examples.

level: juniorimportance: must knowfreq 82%

basics

~10 s

A terminal operator is the function that actually starts a Flow running and consumes its values. Without one, nothing happens because Flows are lazy. Examples: collect and toList.

open as a page

What do the map and filter intermediate operators do on a Kotlin Flow, and why are they 'cold' and 'lazy'?

level: juniorimportance: must knowfreq 80%

basics

~10 s

map turns each emitted value into a new value; filter keeps only values matching a condition. They do nothing until a terminal operator (like collect) runs the flow, and each collection re-runs the work.

open as a page

Explain the ordering and concurrency guarantees of flatMapConcat versus flatMapMerge, including the concurrency argument and its default.

level: middleimportance: must knowfreq 60%

basics

~20 s

Concat processes inner flows one at a time, so output keeps the input order. Merge processes several at once, so faster inner flows can finish first and outputs interleave. Merge lets you cap how many run together; the default is 16.

open as a page

What is the transform { } operator and how does it differ from map and filter? When must you reach for it?

level: middleimportance: must knowfreq 60%

basics

~10 s

transform is the general operator: for each incoming value you can emit zero, one, or many results by calling emit() as often as you like. map and filter are restricted special cases of it.

open as a page

Explain combine's seeding and conflation behavior: when does it first emit, and can intermediate values be skipped?

level: middleimportance: should knowfreq 45%

basics

~10 s

combine emits its first value only after every source has produced one. If a source emits faster than the collector consumes, combine may skip older values and use only the most recent of each.

open as a page

What does merge do with multiple Flows, and how does it differ from zip and combine?

level: middleimportance: should knowfreq 55%

basics

~10 s

merge runs several flows at once and forwards each item as it arrives into one flow. It does not pair or transform values — it just interleaves them in arrival order.

open as a page

Given a few real scenarios (parallel HTTP fan-out, ordered pagination, autocomplete), which flattening operator fits each and why?

level: middleimportance: should knowfreq 55%

basics

~10 s

Autocomplete uses flatMapLatest (drop stale searches). Ordered pagination uses flatMapConcat (keep order). Parallel HTTP fan-out uses flatMapMerge with a concurrency cap (run several at once but bounded).

open as a page

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%

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.

open as a page

Explain reduce and fold as terminal operators on Flow. How do they differ, and what happens with an empty flow?

level: middleimportance: should knowfreq 48%

basics

~10 s

Both combine all emitted values into one result. fold starts from a value you provide; reduce starts from the first emitted value. reduce throws on an empty flow; fold returns the initial value.

open as a page

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%

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.

open as a page

You must build a derived UI state from three independent flows and also fan in raw events from many sources. Which combining operators do you choose and why, and what pitfalls do you guard against?

level: seniorimportance: should knowfreq 30%

basics

~20 s

Use combine to build one UI state from the three inputs because it reacts to the latest of each. Use merge to pool the many same-type event flows into one stream. Avoid zip here — you don't want lockstep pairing.

open as a page

How does zip handle concurrency and backpressure between its two source flows, and what are the implications?

level: seniorimportance: should knowfreq 35%

basics

~10 s

zip collects both flows concurrently but emits a pair only when both have produced the next item. The faster flow waits for the slower one, so the slow side sets the pace.

open as a page

How does flatMapLatest implement cancellation, and what must your inner flow do to honour it correctly?

level: seniorimportance: should knowfreq 50%

basics

~20 s

When a new value arrives, flatMapLatest cancels the coroutine running the previous inner flow and starts a new one. Your inner flow must be cancellation-cooperative — use suspending calls or check for cancellation — so it actually stops.

open as a page

What does launchIn(scope) do, and how does it differ from calling collect? When would you prefer it, and what are the lifecycle implications?

level: seniorimportance: should knowfreq 55%

basics

~20 s

launchIn(scope) starts collecting a flow in the background inside the given scope and returns a Job, instead of suspending the current code like collect does. It's used for fire-and-forget collection tied to a scope's lifecycle.

open as a page

What is onEach and how does it differ from collect? Why is onEach often paired with launchIn, and what does map do that onEach intentionally does not?

level: seniorimportance: should knowfreq 40%

basics

~20 s

onEach runs a side effect for each value but passes the value through unchanged, returning a Flow. collect is terminal and returns Unit. onEach + launchIn lets you start the flow in a scope without writing a collect lambda.

open as a page

What are flattenConcat/flattenMerge, how do they relate to the flatMap* operators, and what should you know about the experimental/stability status of these operators?

level: seniorimportance: nice to knowfreq 30%

basics

~10 s

flattenConcat and flattenMerge flatten an existing Flow<Flow<T>>. flatMapConcat/Merge are just map-then-flatten shortcuts. Some of these operators were experimental and need an opt-in annotation; check your kotlinx-coroutines version.

open as a page

When you call toList() on a flow, on which coroutine/dispatcher does the upstream producer run, and what happens to collection if the surrounding coroutine is cancelled mid-stream?

level: seniorimportance: nice to knowfreq 34%

basics

~10 s

By default the producer runs in the same context as the collector that called toList(). If that coroutine is cancelled while collecting, collection stops with a CancellationException and toList() does not return.

open as a page

How do intermediate operators like map/filter/transform behave with respect to coroutine context and operator fusion? Why does calling withContext inside a map lambda violate context preservation, and what should you use instead?

level: principalimportance: nice to knowfreq 25%

basics

~10 s

By default every operator and emission runs in the collector's coroutine context (context preservation). You must not switch dispatcher inside map with withContext; instead change the upstream context declaratively with flowOn.

open as a page