# How Kotlin Flow Operator Fusion Works and When Ordinary Operators Break It

DevFeed: [How Kotlin Flow Operator Fusion Works and When Ordinary Operators Break It](<https://devfeed.tech/articles/i-counted-every-hidden-channel-kotlin-flow-creates-most-devs-are-wrong-about-which-operators-are-22947.md>)

Original publisher: [Read 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>)

Author: Majidshahbaz

Published: 2026-09-13T05:43:21Z

Content type: article

Language: en

Sources: [ProAndroidDev - Medium](<https://devfeed.tech/sources/proandroiddev-medium.md>)

Topics: [kotlin-flow](<https://devfeed.tech/topics/kotlin-flow.md>), [Coroutines](<https://devfeed.tech/topics/coroutines.md>), [Code](<https://devfeed.tech/topics/code.md>)

Tags: [android](<https://devfeed.tech/tags/android.md>), [code](<https://devfeed.tech/tags/code.md>), [coroutines](<https://devfeed.tech/tags/coroutines.md>), [jetpack-compose](<https://devfeed.tech/tags/jetpack-compose.md>), [kotlin](<https://devfeed.tech/tags/kotlin.md>), [kotlin-coroutines](<https://devfeed.tech/tags/kotlin-coroutines.md>), [kotlin-flow](<https://devfeed.tech/tags/kotlin-flow.md>)

## AI overview

This article investigates Kotlin Flow operator fusion by instrumenting the runtime and comparing pipelines with adjacent fusible operators against pipelines interrupted by an ordinary map() call. It explains that adjacent channelFlow, flowOn, buffer, and produceIn calls can share one underlying channel through FusibleFlow, while an intervening non-fusible operator causes additional wrapper structure.

## Source excerpt

Image Generated for the ArticleI Counted Every Hidden Channel Kotlin Flow Creates -- Most Devs Are Wrong About Which Operators Are Freebuffer() and flowOn() are supposed to fuse into one channel when chained. I wrote code to prove it -- and then found four "harmless" operators that quietly break it. Ask any experienced Kotlin developer what happens when you chain .buffer().flowOn(Dispatchers.IO).buffer(), and you'll usually get a confident answer: "it fuses into one channel." That's the folklore, repeated in talks, docs, and Stack Overflow answers. It's also only half true, and nobody seems to have actually checked the other half: which everyday operators quietly stop that fusion from happening. I decided to stop trusting folklore and instrument the actual runtime. Five separate experiments later, I had a very clear, very measurable answer -- and one of the results genuinely surprised me. The Myth, and the Mechanism Behind It Kotlin's coroutines library really does fuse adjacent channelFlow, flowOn, buffer, and produceIn calls into a single underlying channel instead of creating one per operator. This isn't a rumor -- it's real, deliberate behavior, implemented through an interface called FusibleFlow. Here's the actual check, straight from the source: https://medium.com/media/1cd3b407c1f2b2955b3df4be68ca18b5/href When flowOn() or buffer() is called, it checks whether the flow it's being called on already implements FusibleFlow. If it does, instead of wrapping it in a new object, it calls .fuse() -- which updates the existing object's settings (buffer size, dispatcher) in place. No new object. No new coroutine. No new channel. But that check only succeeds if the upstream is already a ChannelFlow. And that's where the folklore quietly stops being true. Building Something to Actually Measure This I wrote two nearly identical pipelines -- one where the fusible operators sit directly next to each other, and one where a completely ordinary map() sits in the middle: https://medi