What is the transform { } operator and how does it differ from map and filter? When must you reach for it?
answer
- transform = emit 0/1/N per value
- FlowCollector receiver -> call emit()
- map & filter are built on transform
- emitAll forwards another flow
- not a flatMap flattener
basics
~10 stransform 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.
solid answer
~40 stransform { value -> ... } is the most general intermediate operator. Its lambda is suspend and receives the upstream value with a FlowCollector receiver, so you call emit() (or emitAll()) any number of times — zero (acts like filter), once (acts like map), or many (one-to-many expansion). map and filter are built on top of transform. Use transform when a single upstream element must produce a variable number of downstream values, when you need to interleave extra emissions (e.g. a 'fetching' status before the result), or when filtering and mapping in one pass. Because emit is suspend, you can call suspend functions between emissions. Note transform does not flatten Flows — for that use flatMapConcat/Merge/Latest.
code
kotlin · 7 linesflowOf(1, 2, 3).transform { n ->
if (n % 2 == 1) { // filter: only odds
emit("odd:$n") // map-ish first emission
emit("sq:${n * n}") // extra emission -> one-to-many
}
}.collect(::println)
// odd:1, sq:1, odd:3, sq:9go deeper
Knows transform can emit multiple values where map/filter cannot.
Explains the FlowCollector receiver, emit/emitAll, and reimplements map/filter via transform.
Identifies when transform improves clarity/performance vs chaining and notes it is the primitive operator.
Distinguishes transform's sequential forwarding from flatMap concurrency/cancellation semantics and guides API/style choices.
## The general operator `map` is 1-to-1 and `filter` is 1-to-(0 or 1). When you need **1-to-N** (or conditional 0/1/N), use `transform`: ```kotlin public inline fun <T, R> Flow<T>.transform( crossinline transform: suspend FlowCollector<R>.(value: T) -> Unit ): Flow<R> ``` The lambda runs with a **`FlowCollector<R>` receiver**, so inside it `this.emit(x)` (usually written `emit(x)`) pushes a value downstream. You may call `emit` **zero, one, or many times** per upstream value. ## Emulating map and filter ```kotlin // map { f(it) } flow.transform { value -> emit(f(value)) } // filter { p(it) } flow.transform { value -> if (p(value)) emit(value) } ``` In fact, `map` and `filter` in the standard library are implemented via `transform`. ## One-to-many expansion ```kotlin flowOf("a", "b").transform { letter -> emit("Fetching $letter") // status line emit(performRequest(letter)) // suspend call, then the real value } // emits: "Fetching a", <resp a>, "Fetching b", <resp b> ``` `performRequest` can be a `suspend` function because the `transform` lambda is itself `suspend`. ## emitAll To forward all values of another flow inside transform, use `emitAll(otherFlow)`: ```kotlin flow.transform { v -> emitAll(lookupFlow(v)) } ``` ## What transform is NOT for - It is **not** a flattening operator in the flatMap sense; `emitAll` forwards but does not give you the cancel-previous behavior of `flatMapLatest` or the concurrency of `flatMapMerge`. - It preserves the sequential, ordered nature of the chain. ## Why it matters `transform` is the primitive; `map`, `filter`, `onEach`, `withIndex`, `mapNotNull` are conveniences. Knowing it lets you fuse a filter+map, inject markers, or expand elements without chaining several operators.
- Why can't map emit two values for one input?map's contract is exactly one output per input; its lambda returns a single R. Multiple emissions require transform's FlowCollector.emit.
- Inside transform, what does emit suspend on?emit suspends until the downstream is ready to receive, honoring backpressure and cancellation, since the whole chain runs in the collector's coroutine.
map is a vending machine (one coin, one snack); transform is a barista who can hand you nothing, a coffee, or a coffee plus a receipt plus a loyalty stamp.
saying these in an interview costs you the question
- Claiming transform flattens nested Flows like flatMapMerge
- Forgetting emit is available via the FlowCollector receiver
- Saying map can produce many values per input
- Returning a value from transform's lambda instead of calling emit
- Using transform where a simple map/filter reads clearer