What is the precedence and associativity of the Elvis operator, and how does `a ?: b ?: c` evaluate?
answer
- right-associative: a ?: (b ?: c)
- first non-null wins
- lower precedence than arithmetic and infix `to`
- higher precedence than == && ||
- parenthesize when mixing operators
basics
~20 sElvis is right-associative, so a ?: b ?: c means a ?: (b ?: c) — it falls through to the first non-null value left to right. It has low precedence, so combine with other operators carefully using parentheses.
solid answer
~40 s`?:` is **right-associative**: `a ?: b ?: c` parses as `a ?: (b ?: c)`, giving a fallback chain that yields the first non-null operand from left to right (and the last operand if all earlier ones are null). Its precedence is low — below arithmetic, comparison, named infix functions, and the range/`in` operators, but above `&&`, `||`, and assignment. So `a + 1 ?: b` parses as `(a + 1) ?: b`, but mixing with `==` or `&&` usually needs parentheses to be clear, e.g. `(a ?: b) == c`. Because of low precedence, `x ?: y to z` actually means `x ?: (y to z)` since `to` is an infix function binding tighter — a common source of confusion. When in doubt, parenthesize.
code
kotlin · 8 linesval a: String? = null
val b: String? = null
val c = "c"
println(a ?: b ?: c) // c (right-associative chain)
val key: String? = null
// val bad = key ?: "k" to 1 // type error: parses key ?: ("k" to 1)
val pair = (key ?: "k") to 1 // ("k", 1)go deeper
Can read a simple a ?: b ?: c chain as first-non-null-wins.
States right-associativity and the rough precedence relative to arithmetic, infix, comparison and logical operators.
Anticipates the infix-to gotcha and applies parenthesization conventions for readability.
Sets lint/style guidance on operator mixing and reviews for precedence-driven bugs in shared utilities.
## Associativity: right-associative chains The Elvis operator is **right-associative**, meaning a chain groups from the right: ```kotlin a ?: b ?: c // parses as a ?: (b ?: c) ``` Evaluation still proceeds left to right and short-circuits: return `a` if non-null; else evaluate `b ?: c`, returning `b` if non-null, else `c`. The net effect is **"first non-null wins"**, and the final operand `c` is the ultimate default. If `c` is non-null, the whole expression is non-null. ```kotlin val value = primary ?: secondary ?: tertiary ?: "fallback" ``` ## Precedence: where Elvis sits From Kotlin's grammar, operator precedence (high to low) relevant here is roughly: 1. Postfix (`?.`, `!!`, `()`, `[]`) 2. Multiplicative `* / %` 3. Additive `+ -` 4. Range `..`, `..<` 5. **Named infix functions** (e.g. `to`, `and`, `shl`) 6. `?:` **(Elvis)** 7. Comparison `< > <= >=` 8. Equality `== !=` 9. `&&` 10. `||` 11. Assignment Key takeaways: - Arithmetic and infix functions bind **tighter** than Elvis: `a + 1 ?: b` is `(a + 1) ?: b`; `x ?: y to z` is `x ?: (y to z)`. - Comparison/equality and the logical operators bind **looser**: `a ?: b == c` parses as `(a ?: b) == c`. ## Practical gotcha with infix `to` ```kotlin val pair = key ?: "k" to value // means key ?: ("k" to value) ``` This is a type error if `key` is a String, because the right side is a `Pair`. Add parentheses: `(key ?: "k") to value`. ## Style guidance Kotlin style favors explicit parentheses when combining `?:` with comparison, `&&`, `||`, or infix functions, because few readers memorize the table. Chains of pure `?:` (fallback lists) are idiomatic and need no parentheses. ```kotlin // Clear chain — no parens needed val host = configHost ?: envHost ?: "localhost" // Mixed operators — parenthesize for clarity val ok = (status ?: "") == "ACTIVE" ```
- How does `a ?: b == c` parse?As `(a ?: b) == c`, because `?:` binds tighter than `==`.
- Why does `x ?: y to z` surprise people?Infix `to` binds tighter than `?:`, so it parses as `x ?: (y to z)` — the whole right side is a Pair.
saying these in an interview costs you the question
- Saying Elvis is left-associative
- Assuming `?:` binds looser than `&&`/`==`
- Not knowing infix functions bind tighter than Elvis
- Overusing parentheses on plain fallback chains where they're unnecessary