Explain `select`'s clause-ordering bias and the resulting fairness/starvation concerns. How do you mitigate them?
answer
- Bias = first-listed ready clause wins
- Hot first clause starves later clauses in loops
- Mitigate: rotate / randomize order, quotas
- No built-in fair select — your responsibility
- Bias is a feature for priority/control channels
basics
~10 sselect checks clauses top-to-bottom, so if several are ready it always picks the first listed. In a busy loop that can starve later clauses. Rotate or randomize clause order to keep things fair.
solid answer
~40 s`select` is **biased**: when more than one clause is simultaneously ready, the one **listed first** wins (clauses are evaluated in declaration order). In a tight `while(true) { select { ... } }` loop where a high-traffic source is always listed first, lower-listed clauses can be **starved** — never selected because the first is perpetually ready. Mitigations: (1) re-order or **rotate** the clause list each iteration so every source periodically gets priority; (2) randomize ordering; (3) bound how many consecutive wins one source may have, then deprioritize it; or (4) restructure to per-channel coroutines or a `Flow` merge if true fairness matters. There is no built-in fair `select`; fairness is the developer's responsibility. The bias is, however, useful when you want deterministic priority (e.g., always service a control/cancel channel before data).
code
kotlin · 13 linesimport kotlinx.coroutines.channels.*
import kotlinx.coroutines.selects.select
// Rotate priority each iteration to avoid starving later channels
suspend fun fairMerge(chs: List<ReceiveChannel<Int>>, out: SendChannel<Int>) {
var offset = 0
while (true) {
val ordered = chs.drop(offset) + chs.take(offset)
val v = select<Int> { ordered.forEach { c -> c.onReceive { it } } }
offset = (offset + 1) % chs.size
out.send(v)
}
}go deeper
Knows select prefers the first-listed clause when several are ready.
Can describe starvation in a loop and a basic fix like reordering clauses.
Implements rotation/quota mitigations, and deliberately uses bias for priority channels.
Weighs fairness vs determinism at system scale, choosing select vs Flow.merge vs per-source coroutines and defining liveness guarantees.
## The bias rule `select` evaluates its clauses in **declaration (top-to-bottom) order**. If, at the moment of selection, multiple clauses are ready, the **first one listed wins** deterministically. If none is ready, `select` suspends and the first to *become* ready wins. This means `select` is **not fair** by default: ordering encodes priority. ## Why this causes starvation Consider a hot loop multiplexing two channels: ```kotlin while (true) { val msg = select<String> { busy.onReceive { "busy:$it" } // always has data rare.onReceive { "rare:$it" } // occasionally has data } handle(msg) } ``` If `busy` always has an element ready when the loop re-enters `select`, the `busy` clause wins **every** time and `rare` is **starved** — its messages pile up or never get serviced. The bias that gives determinism for ties becomes a liveness bug under sustained load. ## Mitigations ### 1. Rotate the clause order Build the clause list dynamically and rotate it each iteration so a different source gets first priority: ```kotlin val channels = listOf(busy, rare) var offset = 0 while (true) { val ordered = channels.drop(offset) + channels.take(offset) val msg = select<String> { ordered.forEach { ch -> ch.onReceive { "$it" } } } offset = (offset + 1) % channels.size handle(msg) } ``` ### 2. Randomize order Shuffle the clause list each iteration. Removes systematic bias at the cost of determinism. ### 3. Quota / round-robin accounting Track consecutive wins per source; once a source exceeds a quota, drop it from the next `select` so others can proceed. ### 4. Restructure If fairness is critical, avoid `select` polling: give each source its own consumer coroutine feeding a shared output channel, or use `Flow` operators like `merge` (which interleaves) instead of hand-rolled `select` loops. ## When bias is a feature The bias is intentional and useful for **priority handling**: list a control/cancellation/shutdown channel first so it is always serviced before lower-priority data: ```kotlin select { control.onReceive { handleControl(it) } // highest priority data.onReceive { handleData(it) } } ``` ## Summary - Ties resolve to the first-listed clause — deterministic, not fair. - Hot loops can starve lower clauses; mitigate by rotating/randomizing order, quotas, or restructuring. - Exploit bias deliberately for priority channels.
- Is `select` fair if several clauses are ready at once?No. It is biased to the first-listed ready clause. Fairness must be engineered by rotating, randomizing, or quota-limiting clause order.
- When is the bias actually desirable?When you want priority — e.g., always service a control/shutdown channel before data by listing it first.
Like a queue where the manager always serves the leftmost line first — fine until that line never empties and the others wait forever.
saying these in an interview costs you the question
- Claiming select picks a random or round-robin winner by default
- Not recognizing starvation in a hot select loop
- Believing there is a built-in fair select mode
- Overlooking that bias can be exploited for priority
- Suggesting busy-wait polling as the fairness fix