In Ruby, when is the ternary cond ? a : b a good fit, and why are nested or multi-line ternaries discouraged?
answer
- compact two-way if/else
- both branches required
- binds looser than ||, tighter than =
- ?a is a character literal
- Style/NestedTernaryOperator
basics
~20 sRuby's ternary cond ? a : b is a compact if/else expression that returns a or b. It fits a short condition choosing between two short values; nesting or spreading it over lines hurts readability, so use if/elsif or case instead.
solid answer
~40 s`cond ? a : b` is the one-line form of `if cond then a else b end`: it evaluates `cond`, then exactly one of `a` or `b`, and returns that value. It needs both branches — there is no ternary without an else. In Ruby's precedence table `?:` sits below `||` and ranges but above modifier-`rescue` and assignment, so `label = paid ? "ok" : "due"` assigns the chosen string without parentheses. Use it when a short condition picks between two short values. Nested ternaries (`a ? (b ? 1 : 2) : 3`) and ternaries broken across lines are hard to scan; RuboCop's `Style/NestedTernaryOperator` and `Style/MultilineTernaryOperator` (both enabled by default) push them back into `if`/`else` or `case`. Keep spaces around `?` and `:` — `?a` on its own is a character literal.
code
ruby · 19 linesPermit = Struct.new(:zone, :resident, :expired)
permit = Permit.new("A", false, false)
status = permit.expired ? "renew" : "ok"
p status # => "ok"
# Nested ternary: flagged by Style/NestedTernaryOperator
fee = permit.resident ? 0 : (permit.zone == "A" ? 40 : 25)
# Clearer
fee =
if permit.resident
0
elsif permit.zone == "A"
40
else
25
end
p fee # => 40go deeper
Show that cond ? a : b returns a or b, needs both branches, and fits short two-way choices; mention spaces around ? and :.
Explain where the ternary sits in the precedence table, why a compound test deserves parentheses, and which cops flag nested and multi-line ternaries.
Recognise when a ternary is hiding side effects or a boolean-literal branch, and refactor it into an if, a named predicate or a case for three or more outcomes.
Settle ternary style as a team rule — for example parentheses around complex tests — and let RuboCop enforce it so review time goes to logic rather than formatting.
## The ternary is a compact if/else expression Ruby's language reference introduces the ternary as another way to write an if-then-else expression: ```ruby input_type = gets =~ /hello/i ? "greeting" : "other" # is the same as input_type = if gets =~ /hello/i "greeting" else "other" end ``` Key properties: - It is an **expression**: it returns the value of whichever branch ran. - **Exactly one branch is evaluated**; the other is never run. - **Both branches are mandatory.** There is no ternary without the `: else` part; for a one-sided condition use a modifier (`x if y`). ## Where it sits in the precedence table Ruby's precedence table lists `?` `:` below `&&`, `||` and the range operators `..`/`...`, and above modifier-`rescue`, the assignment operators, `defined?`, `not`, `and`/`or` and the statement modifiers. In practice that means: | Code | Parsed as | Note | |---|---|---| | `fee = resident ? 0 : 25` | `fee = (resident ? 0 : 25)` | assignment binds last, so no parentheses needed | | `ok = a \|\| b ? 1 : 2` | `ok = ((a \|\| b) ? 1 : 2)` | the `\|\|` is the test, which surprises readers | | `puts valid ? "yes" : "no"` | `puts((valid ? "yes" : "no"))` | the ternary is the argument | The second row is the classic readability trap: when the test contains operators, parenthesise it so the reader does not have to recall the table. RuboCop's `Style/TernaryParentheses` governs this; its default style in 1.91 is `require_no_parentheses`, and the config notes that `require_parentheses_when_complex` is expected to become the default in the next major release. ## Lexical gotchas: question marks everywhere Ruby method names may end in `?`, and `?a` is a **character literal** (a one-character string, `"a"`). So: - `permit.expired? ? "renew" : "ok"` is fine: the first `?` belongs to the method name, the second is the ternary. - Always put **spaces around `?` and `:`**; squeezing them invites misreadings, because `?a` can be a character literal and `:b` a symbol. ## When the ternary is a good fit Use it when: 1. the condition is **short** and reads as a yes/no question; 2. both results are **short values** (literals, variables, a simple call); 3. the result is being **assigned, returned or passed** — the ternary exists to produce a value. ```ruby badge = permit.resident? ? "R" : "V" fee = permit.expired? ? 0 : permit.daily_rate ``` ## When it is not - **Nested ternaries.** `a ? (b ? 1 : 2) : 3` forces the reader to match `?` and `:` pairs. RuboCop's `Style/NestedTernaryOperator` (enabled) flags it and its autocorrection turns the outer ternary into `if`/`else`. - **Multi-line ternaries.** Spreading `?` and `:` over lines is just an `if` in disguise; `Style/MultilineTernaryOperator` (enabled) says to use `if`/`unless` instead. - **Side effects in branches.** `paid ? charge! : notify!` works, but the ternary signals "choose a value"; an `if` states the intent of doing one of two things. - **Boolean results.** `permit.expired? ? true : false` is redundant: return the predicate call itself. RuboCop's `Style/IfWithBooleanLiteralBranches` (pending, so off until new cops are enabled) catches the `if` and ternary versions of this when the test is a comparison or a predicate method. - **Three or more outcomes.** Use `if`/`elsif`/`else` or `case`. The inverse rule also exists: `Style/OneLineConditional` (enabled) flags `if c then a else b end` written on a single line and prefers the ternary there. The two cops together push short two-way choices into the ternary and everything larger out of it. ## Summary The ternary is Ruby's compact, value-returning two-way conditional. It always has both branches, evaluates only one, and binds looser than `||` but tighter than `=`. Keep the test and both values short, parenthesise a compound test, never nest it, and never spread it across lines.
- How does `ok = a || b ? 1 : 2` parse in Ruby?As `ok = ((a || b) ? 1 : 2)`. The ternary binds looser than `||` and tighter than `=`, so the whole `a || b` becomes the test and the chosen number is assigned. Parenthesising the test makes that explicit for readers who do not have the precedence table memorised.
- Can a Ruby ternary omit the else part?No. The ternary syntax is always `test ? then_value : else_value`. For a one-sided condition use the modifier `value if test`, which evaluates to `nil` when the test is falsy, or a block `if` without `else`.
saying these in an interview costs you the question
- A Ruby ternary evaluates both branches and then picks one of the results.
- fee = resident ? 0 : 25 needs parentheses or it assigns resident to fee.
- Nesting ternaries is fine as long as everything fits on one line.
- You can write cond ? value without the colon part for a one-sided choice.
- valid ? true : false is the idiomatic way to return a boolean from a predicate.