Sitemap

Custom Kotlin Coroutine Races

Find the fastest, then change the rules to filter out nulls or pick your own winner

3 min readApr 3, 2025

--

Photo by Vitolda Klein on Unsplash

Kotlin doesn’t have a built-in race() function like JavaScript or some other languages, but it only takes one line to write your own.

suspend fun <T> race(vararg tasks: suspend () -> T): T =
channelFlow { tasks.forEach { launch { send(it()) } } }.first()

Use race() to run several suspending functions at once, get the result from the fastest, and cancel all the slower ones that didn’t make the cut.

Let’s break it down.

  1. We made a channelFlow.
  2. We launched a coroutine for each task, which will send() the result to the channelFlow when it’s done.
  3. We used first() to grab the first item from the Flow—which will be the result from the first coroutine that managed to complete.

The channelFlow has its own coroutine scope, so all the coroutines act as linked subtasks of the Flow. When first() gets an item, the Flow collection ends, and the remaining coroutines are automatically cancelled.

Let’s try it.

suspend fun slow(): String {
delay(1000)
return "This function is slow 🐌"
}

suspend fun fast(): String {
delay(10)
return "This function is fast 🚀"
}

suspend fun <T> race(vararg tasks: suspend () -> T): T =
channelFlow { tasks.forEach { launch { send(it()) } } }.first()

suspend fun main() {
val result = race(::slow, ::fast)
println(result) // → "This function is fast 🚀"
}

Run it here: https://pl.kotl.in/ip5x4BevO

Filter out nulls and choose a new winner

What if you don’t just want the fastest task, but the fastest task that returns a non-null value? Easy—swap first() for first { it != null }.

suspend fun slow(): String? {
delay(1000)
return "This function is slow 🐌"
}

suspend fun fast(): String? {
delay(10)
return null
}

suspend fun <T> raceNotNull(vararg tasks: suspend () -> T): T =
channelFlow { tasks.forEach { launch { send(it()) } } }.first { it != null }

suspend fun main() {
val result = raceNotNull(::slow, ::fast)
println(result) // → "This function is slow 🐌"
}

Run it here: https://pl.kotl.in/TmKW2jHc2

You can add any flow operator you like to change the rules of the race. Try using filter() to disqualify some tasks, take(n).toList() to pick more than one winner, or even last() to turn the race on its head and have the slowest task win!

Flows and channels aren’t the only way to race coroutines. You can do something similar with select(), or with the Arrow’s library’s raceN() function. But the channelFlow technique gives you the full power of Flow operators, making it easy to customize the race and set your own rules.

--

--

Responses (1)