# Kotlin Flow's Hidden Channel Overhead: Surprising Fusion Breakers

Common operators like map() silently create extra coroutines — here's the proof.

- **Source:** [ProAndroidDev - Medium](https://brifing.si/source/d9553fa9-7b72-4b9b-bc73-c6bf9a40dd5f)
- **Published:** 2026-09-13
- **Topic:** [Software & Engineering](https://brifing.si/c/engineering)
- **Original article:** <https://proandroiddev.com/i-counted-every-hidden-channel-kotlin-flow-creates-most-devs-are-wrong-about-which-operators-are-8f38cf676e07?source=rss----c72404660798---4>
- **On Brifing:** https://brifing.si/s/395748ad-460d-4864-95d2-ea10524b244f

## The Fusion Myth

Kotlin's coroutine library fuses adjacent buffer(), flowOn(), and produceIn() calls into a single channel through FusibleFlow interface. This isn't just theory — it's confirmed in the source code. However, this fusion breaks when certain 'harmless' operators are inserted between fusible calls, creating unexpected overhead. The article's author built instrumentation to measure this effect directly.

## Experimental Proof

Through reflection and coroutine debugging, the author proved that inserting a simple map() transform between buffer() and flowOn() creates: 1) An additional wrapper object in the Flow chain, 2) An extra producer coroutine at runtime, and 3) Three thread hops instead of one. This occurs despite identical functionality — the only difference being operator ordering.

## Unexpected Culprits

Testing revealed four common operators that break fusion: map(), catch(), onEach(), and distinctUntilChanged(). Surprisingly, conflate() maintains fusion because it's internally implemented as buffer(). This distinction matters because each broken fusion case creates separate channels and coroutines — invisible in code but measurable in performance.

## Runtime Impact

Coroutine debug dumps showed fused pipelines use just 2 coroutines (collector + producer), while broken chains require 3. Custom dispatcher logging proved thread hops triple from 1 to 3. These overheads compound in high-throughput scenarios like sensor pipelines or WebSocket feeds, where micro-optimizations matter most.

## The conflation Exception

conflate() behaves differently than other transforms because it's essentially buffer() with different parameters. When substituted for map() in tests, it maintained fusion and reduced coroutine count back to 2. This reveals implementation details matter more than operator appearance — what looks 'lightweight' may carry hidden costs.

## Practical Implications

The findings don't mean avoiding these operators — they're essential tools. Instead, developers should: 1) Group fusible operators together, 2) Place transforms either before or after fused blocks, not between them, and 3) Remember that 'simple-looking' operators aren't necessarily free in Flow pipelines.

## When Optimization Matters

For most UI Flows, these micro-optimizations won't be noticeable. But in high-frequency data streams (10,000+ events/second), each extra coroutine adds scheduling overhead. The article provides concrete measurement techniques to identify such bottlenecks using coroutine debugging and custom dispatchers.

## Beyond Folklore

This investigation proves that even well-documented behaviors like Flow fusion have undocumented edge cases. The methodology — combining source analysis, runtime instrumentation, and controlled experiments — provides a template for verifying other performance assumptions in coroutine-based systems.

## Sources covering this story

- https://proandroiddev.com/i-counted-every-hidden-channel-kotlin-flow-creates-most-devs-are-wrong-about-which-operators-are-8f38cf676e07?source=rss----c72404660798---4 — 

---

_Summary written by Brifing from the original reporting. Copyright in the original article belongs to its publisher; read the full piece at the link above._
