skip to content

What does `onTimeout` do in a `select` expression, and how does it differ from wrapping the `select` in `withTimeout`?

level: seniorimportance: should knowfreq 30%

answer

  1. onTimeout = clause that wins if nothing else ready
  2. Returns a value, no exception thrown
  3. withTimeout cancels + throws TimeoutCancellationException
  4. onTimeout doesn't cancel other clauses' background work
  5. Soft fallback (onTimeout) vs hard SLA (withTimeout)

basics

~10 s

onTimeout(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 lines
kotlin
import 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

for a junior

Knows onTimeout provides a time-based fallback inside select.

for a middle

Distinguishes onTimeout's value-return from withTimeout's exception, and uses it for poll/heartbeat loops.

for a senior

Chooses between soft (onTimeout) and hard (withTimeout) deadlines, handles loser cancellation, and accounts for experimental-API caveats.

for a principal

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

context