
Ever had a fast data source send more items than your app can handle, causing slowdowns or even crashes? Kotlin Flow gives you built-in ways to keep your producer and consumer in step. In this post, we’ll cover:
- What backpressure means
- How Flow’s default “wait for each other” mode works
- When to add a small queue with
buffer()
- How
conflate()
skips old items - Why
collectLatest { }
stops old work on new data - How to pick the right option for your case
What Backpressure Means
Backpressure is simply making sure a fast sender of data doesn’t overwhelm a slower receiver. Without it, you might end up storing too many items in memory or wasting time working on out-of-date information.
Backpressure helps you:
- Keep memory use under control
- Avoid doing unnecessary work
- Make app performance more predictable
1. Default “Wait for Each Other” Mode
By default, when you do:
flow { repeat(3) { emit(it) println("Sent $it") delay(100) // fast sender } } .collect { value -> println("Handling $value") delay(300) // slow handler }
the sender (emit
) will pause until the handler (collect
) finishes with the last value. There’s no queue, each value is sent and handled one at a time.
2. Adding a Small Queue with buffer()
If you want the sender to get ahead by a bit, use:
flow { … } .buffer(capacity = 2) .collect { value -> // slow work here }
- Now the sender can put up to 2 items into a little queue.
- Once the queue is full, it will pause again.
This gives you a limited queue: you still handle every item, but you smooth out spikes in speed.
3. Skipping Old Items with conflate()
When you only care about the newest data, such as updating a progress bar, you can write:
flow { … } .conflate() .collect { value -> println("Update to $value") delay(300) }
- If the handler is busy, only the latest unhandled item is kept.
- Older ones are dropped, so you never process stale updates.
Note:
conflate() does not stop the current work; it just skips old values next time you read.
4. Stopping Old Work with collectLatest { }
To go further and cancel any ongoing work when new data comes in, use:
flow { … } .collectLatest { value -> println("Start $value") delay(300) // might get cut off println("Done $value") }
- On each new
emit
, the block handling the previous value is thrown away right away. - You only finish the work for the very last value if the sender keeps sending faster than you can handle.
This is perfect for search-as-you-type, where you want to drop old requests as soon as the user types again.
5. Choosing the Right Tool
- Plain
collect
What it does: Sender and handler wait on each other, one by one
When to pick it: You must handle every item in order .buffer(n)
What it does: Small queue of size n; no items are dropped
When to pick it: You want a little burst of buffering but still process all items.conflate()
What it does: Keep only the latest item if the handler is busy
When to pick it: You need the freshest data but still want to finish your current workcollectLatest { }
What it does: Cancel any ongoing work as soon as new data arrives
When to pick it: Only the most recent result matters; drop everything else immediately
Job Offers
6. Final Thoughts
- Backpressure keeps fast data streams from overloading slow processors.
- Default mode has no queue: safe but can be slow.
buffer() adds a small queue: more flexibility, no drops.
conflate() skips old values: always fresh, but let current work finish.
collectLatest { } stops old work: only finish the latest item.
Next time your Flow seems too fast or too slow, ask yourself:
- Do I need to handle every value?
- Would a small queue help?
- Is only the newest data important?
- Should I cancel old work when new data arrives?
Pick the simplest option that fits, and Kotlin Flow will handle the rest.
This article was previously published on proandroiddev.com.