skip to content

Why does Go have no ternary operator, and what do you write instead?

level: juniorimportance: should knowfreq 55%

answer

  1. one obvious way to write a branch
  2. the designers saw it abused elsewhere
  3. declare the variable, then if
  4. two builtins arrived in Go 1.21
  5. a helper evaluates both sides eagerly

basics

~20 s

Go omits the ?: ternary because it was too often used to build dense, nested expressions. Idiomatic Go declares the variable and follows it with a short if/else; the min and max builtins cover the common comparison case.

solid answer

~50 s

Go's designers left out `?:` on readability grounds: in the languages that have it, the operator is regularly nested and combined until the expression is unreadable, and Go's stated goal is one obvious way to write a branch. So you declare the variable with its fallback value and overwrite it in an `if`, or use an `if`/`else` around a single assignment. Since Go 1.21 the most common ternary — picking the smaller or larger of two ordered values — is covered by the `min` and `max` builtins. What you genuinely lose is expression-position branching: you cannot branch inside a struct literal, a `const` declaration, or an argument list without either hoisting the value into a variable first or writing a helper function, and a helper evaluates both branches eagerly because Go has no lazy arguments.

code

go · 8 lines
go
// no ternary: declare the variable, then branch
limit := defaultLimit
if userLimit > 0 {
	limit = userLimit
}

// "smaller of the two" is a builtin (Go 1.21+), not an if
pageSize := min(requested, 500)

go deeper

for a junior

Be ready to say plainly that Go has no ?: and to write the replacement on a whiteboard: declare the variable with a default, then an if that overwrites it. Knowing the min and max builtins exist is a bonus.

for a middle

Explain the reasoning, not just the workaround: the operator nests into unreadable expressions, and Go trades keystrokes for one obvious reading. Be able to name what is actually lost — branching inside a literal or a const.

for a senior

Show judgment about the helper-function workaround: it compiles, but eager argument evaluation makes it unsafe around nil checks and side effects. That is the answer that separates having read about it from having reviewed it.

for a principal

Frame it as a consistent language-design posture rather than one missing operator, and be ready to hold that line in a style discussion when a team arriving from another language wants a ternary helper package.

## The absence Go has no conditional expression. There is no `cond ? a : b`, and there is no `if` that yields a value the way an expression-oriented language's `if` does. Every conditional in Go is a **statement**: it performs effects, it does not produce a value. ## The stated reason The Go FAQ answers this directly: the operator was left out because the designers had seen it used too often to create impenetrably complex expressions. `?:` composes with itself. `a ? b : c ? d : e ? f : g` is legal in the languages that have it, and it appears in real code. An `if`/`else` chain does not compress that way — the syntax forces the branches onto their own lines, where a reader and a diff can see them. This is the same instinct behind Go's other omissions: the language prefers a construct that is slightly verbose but has exactly one reading over a construct that is terse and has several. The cost is paid by the writer, once; the benefit is collected by every later reader. ## What you write instead The standard shape is declare-then-override: ```go limit := defaultLimit if userLimit > 0 { limit = userLimit } ``` Or the symmetric form when neither branch is a natural default: ```go var scheme string if secure { scheme = "https" } else { scheme = "http" } ``` Five lines where another language writes one. In exchange, the two branches are both visible, both greppable, and either one can grow a second statement without a rewrite. ## The builtins that absorb the common case Go 1.21 added `min` and `max` as builtins over any ordered type, and they retire a large share of real-world ternaries: ```go pageSize := min(requested, 500) ``` Before 1.21 that needed a three-line `if` or a hand-written helper per type. Note that `math.Min` and `math.Max` are *not* the same thing — they are `float64`-only functions that predate the builtins, and routing integers through them means two conversions and a loss of precision above 2^53. For integers, use the builtins. ## Where the absence actually hurts Three places, all of them expression positions: 1. **Struct literals and argument lists.** You cannot pick a field value conditionally inside the literal; you compute it into a variable on the line above. 2. **Constant declarations.** A `const` cannot branch at all, so a value that depends on a condition simply cannot be a constant — it becomes a `var` or a function. 3. **Long chains of small choices.** Building a config struct where six fields each have a fallback turns into six three-line blocks. This is the case where people reach for a helper. ## Why a generic helper is not the answer Since Go 1.18 you *can* write one: ```go func pick[T any](cond bool, a, b T) T { if cond { return a } return b } ``` It compiles, and the community still does not use it, for a concrete reason rather than a stylistic one: **Go evaluates all arguments before the call**. `pick(p != nil, p.Name, "anonymous")` dereferences `p` unconditionally and panics, where the equivalent `if` would not. The same eagerness means both an expensive computation and a side effect in the losing branch still run. A ternary in a language that has one short-circuits; a function call cannot. If you introduce such a helper, restrict it to cheap, side-effect-free, already-computed values, and expect a reviewer to push back. ## How to answer the "but it's more verbose" objection The honest answer is: yes, and the trade was made on purpose. Go accepts extra lines to buy a single reading of every branch. The same trade shows up in the absence of implicit numeric conversion and in errors being returned rather than thrown. Someone onboarding from a ternary-rich language should hear that framing rather than a claim that the `if` form is somehow better in isolation — it isn't, it's just harder to abuse.

  • What do you lose by having no conditional expression, concretely?
    Expression-position branching. You cannot choose a field value inside a struct literal, an argument inside a call, or a value inside a `const` declaration — the value has to be computed into a variable on a line above first. For a config struct with several fallbacks that turns one literal into a run of small `if` blocks, which is the strongest honest complaint against the omission.
  • Why doesn't the community just use a generic pick(cond, a, b) helper?
    Because Go evaluates every argument before the call, so both branches run. `pick(p != nil, p.Name, "anon")` dereferences `p` unconditionally and panics; an `if` would not. Side effects and expensive calls in the losing branch also execute. The helper is only safe for cheap, side-effect-free values you have already computed.
  • Is math.Min a reasonable substitute for the min builtin on integers?
    No. `math.Min` takes and returns `float64`, so using it on integers means converting in and out, and any `int64` beyond 2^53 cannot round-trip through `float64` exactly. The `min` and `max` builtins added in Go 1.21 work on any ordered type, including all integer types and strings, with no conversion.

saying these in an interview costs you the question

  • Claims && and || return one of their operands like in Python
  • Says the if/else form is slower than a ternary would be
  • Proposes a pick helper without noting both arguments evaluate
  • Uses math.Min on int64 values and loses precision
  • Insists Go simply forgot the operator rather than rejecting it