What does `onTimeout` do in a `select` expression, and how does it differ from wrapping the `select` in `withTimeout`?
answer
- onTimeout = clause that wins if nothing else ready
- Returns a value, no exception thrown
- withTimeout cancels + throws TimeoutCancellationException
- onTimeout doesn't cancel other clauses' background work
- Soft fallback (onTimeout) vs hard SLA (withTimeout)
basics
~10 sonTimeout(duration) is a clause that wins if no other clause becomes ready within the time. It lets select fall back gracefully. Unlike withTimeout, it returns a value instead of throwing or cancelling.
solid answer
~40 s`onTimeout(duration) { ... }` adds a time-based clause to `select`: if no other clause is ready before the duration elapses, the timeout clause wins and runs its lambda, returning a normal value. It is a **non-throwing, inline deadline** for the whole `select`. By contrast, wrapping `select` in `withTimeout(duration) { select { ... } }` cancels the `select` (and any clauses tied to it) and throws `TimeoutCancellationException` when the deadline passes — control leaves via exception, not a clean return. Use `onTimeout` when 'nothing happened in time' is a normal branch you want to handle with a fallback value; use `withTimeout`/`withTimeoutOrNull` when exceeding the deadline is an error or cancellation condition. Note `onTimeout` is part of the experimental selects API and takes a `Duration` (or millis) parameter.
code
kotlin · 10 linesimport kotlin.time.Duration.Companion.seconds
import kotlinx.coroutines.*
import kotlinx.coroutines.channels.*
import kotlinx.coroutines.selects.select
suspend fun nextOrHeartbeat(ch: ReceiveChannel<String>): String =
select {
ch.onReceiveCatching { res -> res.getOrNull() ?: "closed" }
onTimeout(1.seconds) { "heartbeat" }
}go deeper
Knows onTimeout provides a time-based fallback inside select.
Distinguishes onTimeout's value-return from withTimeout's exception, and uses it for poll/heartbeat loops.
Chooses between soft (onTimeout) and hard (withTimeout) deadlines, handles loser cancellation, and accounts for experimental-API caveats.
Designs layered deadline/SLA strategies, reasons about cancellation propagation and CancellationException semantics across the call tree.
## `onTimeout` — a clause, not an exception `onTimeout(duration) { ... }` is a `SelectBuilder` clause that becomes **ready after `duration` elapses** with no other clause having won. When it wins, its lambda runs and `select` returns that lambda's value — completely normally, no exception. ```kotlin import kotlin.time.Duration.Companion.milliseconds import kotlinx.coroutines.selects.select suspend fun pollOrDefault(ch: ReceiveChannel<Int>): Int = select { ch.onReceive { it } onTimeout(200.milliseconds) { -1 } // fallback value } ``` Here, if nothing arrives within 200 ms, `select` returns `-1`. The channel is untouched; you can loop and `select` again. ## `withTimeout` — cancellation + exception `withTimeout(duration) { block }` runs `block` under a deadline. If the deadline passes, it **cancels the block** and throws `TimeoutCancellationException` (a subclass of `CancellationException`). `withTimeoutOrNull` is the same but returns `null` instead of throwing. ```kotlin val result: Int? = withTimeoutOrNull(200.milliseconds) { select { ch.onReceive { it } } } ``` ## Key differences | Aspect | `onTimeout` clause | `withTimeout(...) { select { } }` | |---|---|---| | Control flow on expiry | Normal return of the lambda's value | Throws `TimeoutCancellationException` (or null with `OrNull`) | | Cancellation | Does not cancel the `select` itself — the timeout simply wins | Cancels the wrapped block (and its current suspension point) | | Granularity | Per-`select`, inline as one of the clauses | Around the whole block, possibly more than the `select` | | Intent | 'Nothing ready in time' is a valid, expected branch | Exceeding the deadline is an error/abort | ## When to use which - **`onTimeout`:** heartbeat/poll loops, returning a default, retrying — where a timeout is part of normal logic and you want a clean value. - **`withTimeout`/`withTimeoutOrNull`:** enforcing an overall SLA where blowing the deadline should abort the operation (and trigger cleanup via the thrown `CancellationException`). ## Gotchas - `onTimeout` does not cancel sibling operations behind other clauses — e.g., an `onAwait` loser keeps running; cancel it explicitly. - Mixing both is valid: an inner `onTimeout` for a soft fallback and an outer `withTimeout` for a hard ceiling. - The selects API (including `onTimeout`) is marked experimental; APIs/signatures can shift between coroutine versions.
- If `onTimeout` wins while an `onAwait` clause's async is still running, is that async cancelled?No. `onTimeout` simply wins the select; the losing async keeps running until you cancel it or its scope ends.
- Why might you prefer `withTimeoutOrNull` over an `onTimeout` clause?When the deadline should abort and cancel the whole suspending block rather than be treated as a normal branch returning a fallback value.
saying these in an interview costs you the question
- Claiming onTimeout throws TimeoutCancellationException
- Saying onTimeout cancels the other clauses' work
- Confusing onTimeout's clean return with withTimeout's exception flow
- Not knowing withTimeoutOrNull returns null instead of throwing
- Assuming onTimeout is stable/non-experimental API