skip to content

Both conflate() and collectLatest help a slow consumer keep up with a fast producer. How do they differ in what they do with the in-progress work?

level: seniorimportance: should knowfreq 40%

answer

  1. conflate = finish then skip (non-preemptive)
  2. collectLatest = cancel + restart (preemptive)
  3. conflate intermediate; collectLatest terminal
  4. collectLatest needs a suspension point to cancel
  5. Cheap renders -> conflate; expensive obsolete work -> collectLatest

basics

~10 s

conflate() lets the current item finish, then jumps to the newest waiting value. collectLatest cancels the current work as soon as a newer value arrives and restarts with it. One finishes-then-skips, the other interrupts.

solid answer

~50 s

Both keep a slow consumer current with a fast producer, but they handle the *in-flight* item differently. conflate() never interrupts: the collector runs each value to completion, and only when it's free does it pick up the latest buffered value (dropping the intermediates). collectLatest does the opposite: when a new value arrives, it cancels the suspending block still processing the previous value and relaunches the block with the new value. So conflate is non-preemptive (no wasted-but-no-cancellation), while collectLatest is preemptive — it needs the work to be cancellable/suspending to actually abort early. Choose conflate when each render is cheap and atomic and you just want the freshest input; choose collectLatest when processing a value is expensive/long and a newer value makes the in-progress work obsolete (e.g., restart a network search on each keystroke). Note collectLatest is the terminal-operator sibling of conflate's intermediate-operator role.

go deeper

for a junior

Knows both give 'latest wins' and that one finishes work while the other restarts.

for a middle

Correctly states conflate finishes the current item while collectLatest cancels and restarts.

for a senior

Explains preemptive vs non-preemptive, the suspension-point requirement for cancellation, and picks the right one by work cost.

for a principal

Reasons about wasted compute, cancellation correctness, and designs the pipeline (cheap render vs cancel-and-restart) accordingly.

## The shared goal When a producer outpaces the collector, you usually only care about the **latest** value. Both `conflate()` and `collectLatest { }` deliver "latest wins", but they treat the **already-running** processing block differently. (This question lives at the conflate↔collectLatest boundary; the focus here is the contrast, not collectLatest's full mechanics.) ## conflate() — finish, then skip `conflate()` is an **intermediate** operator. It buffers one value (`buffer(1, DROP_OLDEST)`). The collector block runs **to completion** for whatever value it started; it is never cancelled. When it returns and asks for the next value, it gets the **latest** one currently buffered, and the intermediates that arrived meanwhile are dropped. - Non-preemptive: in-flight work is honoured. - Works even if the collector body is non-cancellable. ```kotlin flow { repeat(5) { delay(50); emit(it) } } .conflate() .collect { delay(200); println("done $it") } // each println completes ``` ## collectLatest — cancel and restart `collectLatest { }` is a **terminal** operator. Each emission launches the block in a coroutine; when a **newer** value arrives, the block processing the previous value is **cancelled** and a fresh one starts with the new value. ```kotlin flow { repeat(5) { delay(50); emit(it) } } .collectLatest { delay(200); println("done $it") } // earlier blocks get cancelled mid-flight ``` Because it relies on coroutine cancellation, the block must hit a **suspension point** (e.g. `delay`, a suspending call) for the cancel to take effect — otherwise a tight CPU loop runs to completion anyway. ## Side-by-side | Aspect | conflate() | collectLatest | |---|---|---| | Operator kind | intermediate | terminal | | In-flight value | runs to completion | cancelled when newer arrives | | Preemptive? | no | yes | | Needs cancellable work | no | yes (suspension point) | | Good for | cheap, atomic renders | expensive work made obsolete by newer input | ## Choosing between them - **conflate():** repaint a progress bar, draw the latest sensor value — each render is quick and you don't want to abort it. - **collectLatest():** restart an expensive autocomplete query on every keystroke — finishing the old query is wasted, so cancelling is correct. The practical risk of `conflate()` here: if each item is slow, you can still lag because conflate won't cancel the slow in-flight work; collectLatest would.

  • Why might collectLatest fail to actually interrupt long work?
    Cancellation is cooperative — it only takes effect at a suspension point. A tight CPU-bound loop with no suspending call won't be cancelled and will run to completion despite a newer value arriving.
  • Can you combine them, e.g. conflate() before collectLatest?
    It is redundant — collectLatest already handles latest-wins by cancelling, so conflate() in front adds little. Pick one based on whether you want to interrupt in-flight work.

conflate is a chef who finishes the current dish then takes the newest order; collectLatest scrapes the half-cooked dish into the bin the moment a newer order comes in.

saying these in an interview costs you the question

  • Says both cancel in-flight work (only collectLatest does)
  • Claims conflate() can interrupt a slow collector body
  • Thinks collectLatest always aborts even CPU-bound work
  • Confuses which is intermediate vs terminal

context