How do you race two suspending operations and take the faster result using `onAwait` in `select`?
answer
- async -> Deferred; deferred.onAwait { result }
- select returns the first deferred to complete
- select does NOT cancel losers — cancel them yourself
- Winning deferred's exception propagates out
- Wrap in coroutineScope for structured safety
basics
~10 sStart each operation with async to get a Deferred, then use select with each deferred's onAwait clause. select resumes with whichever finishes first. Remember to cancel the loser.
solid answer
~40 s`Deferred<T>.onAwait { result -> ... }` is a `select` clause that becomes ready when that `async` computation completes; on winning it provides the result. To race, launch both with `async` inside a coroutine scope, then `select { d1.onAwait { ... }; d2.onAwait { ... } }`. The winner's result is returned. Crucially, `onAwait` only awaits — it does **not** cancel the loser, so you must cancel the slower `Deferred` yourself (or wrap the whole thing in a scope you cancel, e.g. via `coroutineScope` plus explicit `cancel()`), otherwise the loser keeps running and may leak resources. If the winning deferred completed exceptionally, `onAwait` rethrows that exception into the `select`.
code
kotlin · 12 linesimport kotlinx.coroutines.*
import kotlinx.coroutines.selects.select
suspend fun firstOf(urls: List<String>, fetch: suspend (String) -> String): String =
coroutineScope {
val jobs = urls.map { url -> async { fetch(url) } }
try {
select { jobs.forEach { d -> d.onAwait { it } } }
} finally {
jobs.forEach { it.cancel() }
}
}go deeper
Knows you use async + onAwait to get whichever completes first.
Understands losers are not auto-cancelled and that the winner's exception propagates, and cancels losers in a finally.
Wraps the race in coroutineScope, reasons about resource cleanup, timeouts, and exception flow across winner/loser.
Designs hedged-request strategies (latency vs cost), back-pressure, and cancellation guarantees for production racing at scale.
## Racing with `onAwait` An `async { }` returns a `Deferred<T>` — a future-like handle whose result you normally get via `await()`. Inside `select`, the clause `deferred.onAwait { result -> ... }` becomes **ready when the deferred completes**, and on winning passes the computed `result` to the lambda. ### The pattern ```kotlin import kotlinx.coroutines.* import kotlinx.coroutines.selects.select suspend fun fastest(): String = coroutineScope { val a = async { slowSource("A") } val b = async { slowSource("B") } select<String> { a.onAwait { winner -> winner } b.onAwait { winner -> winner } }.also { a.cancel(); b.cancel() // cancel both; the winner is already done, the loser is stopped } } ``` ### Why you must cancel the loser `select` only **resumes** with the first ready clause; it does **not** automatically cancel the operations behind the losing clauses. With `onAwait`, the losing `async` job keeps running in the background, wasting CPU, holding connections, or completing with an uncaught exception. Always cancel the losers — cancelling the already-completed winner is a harmless no-op, so cancelling both is simplest. Using `coroutineScope { }` adds a safety net: structured concurrency ensures children are cancelled if the scope itself is cancelled or fails, but within a successful `select` you still cancel explicitly to stop the loser promptly. ### Exceptions If the **winning** deferred completed exceptionally, `onAwait` propagates that exception out of `select`. A loser that fails after losing will not affect the `select` result but, if not cancelled, its failure may surface through the parent job — another reason to cancel losers. ### Adding a deadline Combine with `onTimeout` to bound the race: ```kotlin select<String?> { a.onAwait { it } b.onAwait { it } onTimeout(2.seconds) { null } } ``` If neither finishes in time, the timeout clause wins and you fall back — then cancel both deferreds. ## Summary - `onAwait` = ready when the `Deferred` completes. - `select` returns the first completed result; the rest keep running unless you cancel them. - Winner's exception propagates; cancel losers to avoid leaks and stray failures.
- Does `select` cancel the losing `async` jobs automatically?No. `onAwait` only awaits completion; the loser keeps running until you cancel it (or the enclosing scope is cancelled).
- What if the winning deferred completed with an exception?`onAwait` rethrows that exception out of the `select` expression, just as `await()` would.
Like sending two couriers and using the first parcel that arrives — but you must call back the other courier or they keep driving.
saying these in an interview costs you the question
- Believing select cancels the losing coroutines for you
- Forgetting to cancel losers, causing leaks or stray exceptions
- Thinking onAwait swallows the winner's exception
- Calling await() on losers afterward instead of cancel()
- Not using a structured scope (coroutineScope) around the race