skip to content

Compare mapLatest and transformLatest with map/transform. What exactly gets cancelled, and how do they relate to collectLatest?

level: middleimportance: should knowfreq 45%

answer

  1. map/transform finish each value; Latest variants cancel previous
  2. mapLatest = 1→1, transformLatest = 1→many (uses emit)
  3. Intermediate (return Flow) vs collectLatest terminal
  4. Same cooperative-cancellation rule
  5. flatMapLatest = switch to new inner flow, cancel old

basics

~20 s

map and transform run their lambda fully for each value. mapLatest and transformLatest start the lambda for each value but cancel the previous lambda when a new value comes in. They are the same 'latest wins' idea as collectLatest, but as intermediate operators that produce a new Flow.

solid answer

~50 s

map/transform are intermediate operators: for each upstream value they run the transform fully and emit downstream. mapLatest and transformLatest run the same transform but cancel the in-flight transform coroutine when a newer upstream value arrives, so only the latest transform completes and emits. transformLatest gives the block a FlowCollector (via emit), so it can emit zero, one, or many values per input — but a fresh upstream value cancels any pending emissions from the previous block. mapLatest is the special case of one value out per input. They are 'Latest' siblings of collectLatest: same cancel-previous semantics, but they yield a Flow you can chain further instead of being terminal. Same cooperative-cancellation rule applies — the transform needs a suspension point. Classic use: flatMapLatest for switching to a new inner flow (e.g. new search query) is the related higher-order operator.

code

kotlin · 13 lines
kotlin
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*

fun main() = runBlocking {
    flowOf(1, 2, 3)
        .transformLatest { v ->
            emit("start-$v")
            delay(100)
            emit("end-$v") // only end-3 survives
        }
        .collect { println(it) }
    // start-1, start-2, start-3, end-3
}

go deeper

for a junior

Knows the Latest variants cancel the previous block like collectLatest.

for a middle

Distinguishes mapLatest (1→1) from transformLatest (1→many emit) and intermediate vs terminal.

for a senior

Connects them to flatMapLatest for inner-flow switching and recalls the shared cooperative-cancellation caveat.

for a principal

Chooses between mapLatest/transformLatest/flatMapLatest by the unit of cancellation (transform vs full subscription) and side-effect safety.

## Operator families - **`map { v -> ... }`** — intermediate operator; for each upstream value runs the lambda to completion and emits exactly one transformed value downstream. - **`transform { v -> emit(...) }`** — intermediate; the lambda is a `suspend FlowCollector<R>.(T) -> Unit`, so you can `emit` **zero or more** values per input. `map` and `filter` are built on it. - **`collect`** — terminal; consumes values, emits nothing downstream. ## The 'Latest' variants Each has a `...Latest` sibling that adds cancel-previous semantics: | Plain | Latest | Kind | |---|---|---| | `map` | `mapLatest` | intermediate, 1→1 | | `transform` | `transformLatest` | intermediate, 1→many | | `collect` | `collectLatest` | terminal | In all three, the block for a value runs in a child coroutine; when a **newer upstream value arrives**, that child is **cancelled** and a new one starts. Only the latest input's block runs to completion (and, for the intermediate ones, only its emissions reach downstream). ```kotlin flowOf(1, 2, 3) .mapLatest { v -> delay(100); v * 10 } .collect { println(it) } // prints only 30 ``` For `transformLatest`, pending `emit`s from a superseded block never reach downstream: ```kotlin flowOf("a", "b") .transformLatest { s -> emit("$s-1") delay(50) emit("$s-2") // for "a" this is cancelled by "b" } .collect { println(it) } // a-1, b-1, b-2 ``` ## Relationship to collectLatest They share **identical cancellation semantics**; the only difference is position in the pipeline: `mapLatest`/`transformLatest` are **intermediate** (return a new `Flow`, can be chained), while `collectLatest` is **terminal** (consumes the flow). Same **cooperative-cancellation** caveat: the transform needs a suspension point to actually be interruptible. ## Related: flatMapLatest `flatMapLatest { v -> someFlow(v) }` is the higher-order cousin: it switches to a **new inner flow** per upstream value and cancels the previous inner flow's collection. It is the canonical 'restart the request on each new query' operator. `mapLatest` cancels a transform; `flatMapLatest` cancels a whole inner-flow subscription.

  • What can transformLatest do that mapLatest cannot?
    transformLatest gives a FlowCollector so its block can emit zero, one, or many values per input; mapLatest emits exactly one.
  • How is flatMapLatest different from mapLatest?
    flatMapLatest switches to and collects a new inner Flow per value, cancelling the previous inner flow; mapLatest just cancels and reruns a transform that yields a single value.

saying these in an interview costs you the question

  • Saying mapLatest can emit multiple values per input (that is transformLatest)
  • Calling collectLatest an intermediate operator
  • Forgetting the transform also needs a suspension point to be cancellable
  • Confusing flatMapLatest (inner-flow switch) with mapLatest
  • Thinking the Latest variants run transforms in parallel

context