Go ternary operator: idiomatic alternatives to `?:` (if, switch, min, cmp.Or)

Go has no ?: ternary; idiomatic equivalent is if/else or switch. Covers lazy branches, min/max builtins, cmp.Or defaults, generic pick helpers, pitfalls of eager helpers, optional julien040/go-ternary, links to if/else, switch, modules, and official Go FAQ.

Published

Updated

Read time 6 min read

Reviewed byDeepak Prasad

Go ternary operator: idiomatic alternatives to `?:` (if, switch, min, cmp.Or)

What is the idiomatic Go equivalent of C’s ternary operator (condition ? a : b)? Use an explicit if / else, sometimes a switch, or a small function—there is no ?: in the language by design (Go FAQ). For value picks that match built-ins, prefer min and max (Go 1.21+). For “first non-zero default”, use cmp.Or (Go 1.22+). This page walks through those patterns, lazy evaluation, and when a tiny third-party helper is still optional.

For control-flow basics, see if / else, variable scope around short if declarations, loops, structs, and modules / getting started.

Tested with Go 1.24 on Linux (min / max require 1.21+; cmp.Or requires 1.22+).


Why Go has no ?:

The FAQ states that if / else is intentionally the only form for that kind of conditional: ternary expressions were often used to build unreadable nested logic in other languages. Go trades one-line brevity for scan-friendly control flow.


Pattern 1: if / else assignment (the default)

Declare a variable, then assign in each branch—this is the direct analogue of result = cond ? a : b:

go
package main

import "fmt"

func main() {
	age := 20
	var label string
	if age >= 18 {
		label = "adult"
	} else {
		label = "minor"
	}
	fmt.Println(label)
}
Output

Output: adult.


Pattern 2: Short if with initialization

When the condition uses a value you only need in the if, use the init statement form (if details):

go
package main

import (
	"fmt"
	"strings"
)

func main() {
	s := "hello"
	if n := len(s); n > 4 {
		fmt.Println("long:", n)
	} else {
		fmt.Println("short")
	}
	fmt.Println(strings.ToUpper(s))
}
Output

Pattern 3: switch instead of if / else if chains

When one expression drives several outcomes, a tagless switch on true or a switch on the value is idiomatic in Go and often clearer than stacked ternaries in C:

go
package main

import "fmt"

func grade(score int) string {
	switch {
	case score >= 90:
		return "A"
	case score >= 80:
		return "B"
	default:
		return "C"
	}
}

func main() {
	fmt.Println(grade(85))
}
Output

Output: B.


Pattern 4: min / max (replaces many numeric ternaries)

A very common use of ?: in C is a < b ? a : b. Since Go 1.21, use the predeclared min and max; they accept multiple ordered arguments (including strings and floats):

go
package main

import "fmt"

func main() {
	fmt.Println(min(3, 7))
	fmt.Println(max("beta", "alpha"))
}
Output

Output:

text
3
beta

For the smallest or largest element of a slice, use slices.Min / slices.Max (Go 1.21+) with a non-empty slice.


Pattern 5: cmp.Or for “first non-zero, else default”

Not a boolean ternary, but the same place people reach for ?: when the condition is really “use x unless it is zero.” Since Go 1.22, cmp.Or returns the first argument not equal to the zero value of type T (T must be comparable):

go
package main

import (
	"cmp"
	"fmt"
)

func main() {
	user := ""
	fmt.Println(cmp.Or(user, "guest"))
	fmt.Println(cmp.Or(0, 0, 42))
}
Output

Output:

text
guest
42

If you need a predicate more complex than “non-zero,” stay with if / else.


Pattern 6: Lazy evaluation (only one branch runs)

Function arguments are always evaluated before the call. There is no language-level lazy ?:. To run only one side, use if / else or an immediately invoked function literal:

go
package main

import "fmt"

func main() {
	val := -5
	idx := func() int {
		if val > 0 {
			return val
		}
		return -val
	}()
	fmt.Println(idx)
}
Output

Output: 5. Use this shape when a branch calls something expensive or has side effects.


Pattern 7: Generic one-line pick (your own helper)

If you want a reusable “choose a or b” without a third-party module, a two-line generic function keeps lazy semantics only if you pass values already computed—for lazy work, pass func() thunks or use if / else:

go
package main

import "fmt"

func pick[T any](cond bool, a, b T) T {
	if cond {
		return a
	}
	return b
}

func main() {
	fmt.Println(pick(5 > 3, "high", "low"))
}
Output

Output: high. The compiler can inline tiny helpers like this; readability still beats micro-shaving lines.


Pattern 8: Optional pointers and zero values

For *T, idiomatic code uses an explicit nil check rather than a ternary:

go
package main

import "fmt"

func label(p *string) string {
	if p != nil {
		return *p
	}
	return "anonymous"
}

func main() {
	fmt.Println(label(nil))
	s := "Ada"
	fmt.Println(label(&s))
}
Output

Optional third-party module: github.com/julien040/go-ternary

The package on pkg.go.dev/github.com/julien040/go-ternary exposes ternary.If, ternary.IfFunc, and related helpers. Typical signature:

text
func If[T any](condition bool, a, b T) T

Add it with go get github.com/julien040/go-ternary@latest inside a module. Snippets below use {run=false} because they expect that dependency in go.mod.

go
package main

import (
	"fmt"

	"github.com/julien040/go-ternary"
)

func main() {
	fmt.Println(ternary.If(true, "yes", "no"))
	fmt.Println(ternary.If(len("abc") > 2, 10, 0))
}

You should see yes then 10. Prefer upstream docs for IfFunc / Iff when you need deferred evaluation via function values.

Eager evaluation pitfall

Both a and b are evaluated before If runs. This program prints both side-effect lines:

go
package main

import (
	"fmt"

	"github.com/julien040/go-ternary"
)

func expensive() int { fmt.Println("expensive side"); return 1 }
func cheap() int     { fmt.Println("cheap side"); return 2 }

func main() {
	useFast := true
	_ = ternary.If(useFast, expensive(), cheap())
}

For lazy branching, use if / else or thunk-based APIs from the same module.


What not to do (readability)

  • Deeply nested ternary.If (or nested pick calls) usually loses to a flat switch or local variables—same problem Go avoided by omitting ?:.
  • map[bool]T tricks (m[cond]) save characters but obscure control flow; reviewers and tools treat explicit if more kindly.

Summary

Go’s answer to “C ternary” is not a single operator: use if / else, switch, min / max, cmp.Or where it fits, function literals when a branch must not run, or a small helper (generic or not). Third-party ternary.If mimics syntax but eagerly evaluates both branch values—know that before using it in hot paths or when branches have side effects. Keep functions and methods straightforward unless a one-liner genuinely helps the reader.


References


Frequently Asked Questions

1. Does Go have a ternary operator like C or JavaScript?

No; the language designers prefer explicit if and switch so control flow stays obvious. See the official Go FAQ entry linked in References.

2. What is the idiomatic Go equivalent of the C ternary operator?

Use a short if/else (often with a predeclared variable), a switch when one expression drives many branches, or a tiny named function. For “pick the smaller/larger of two values”, use the built-in min and max since Go 1.21 instead of hand-written (a < b ? a : b).

3. How do I avoid evaluating both sides of a conditional (lazy ternary)?

Use ordinary if/else, or call a function literal so only the taken branch runs; passing both results as function arguments always evaluates both before the call.

4. What is cmp.Or in Go?

Since Go 1.22, cmp.Or returns the first argument that is not the zero value for its comparable type—useful for “user string or default” style defaults, not for arbitrary boolean conditions.

5. Does ternary.If from julien040/go-ternary evaluate both branches?

Yes; both arguments are evaluated before the function runs, unlike lazy ternaries in some languages, so avoid expensive calls on the unused side.

6. When is a helper like ternary.If appropriate?

Sparingly, for simple same-type picks where both sides are cheap constants or variables; prefer normal if/else when readability or side effects matter.

7. How do I add go-ternary to a module?

Run go get github.com/julien040/go-ternary@latest inside a module root after go mod init; see the module tutorial linked below.
Deepak Prasad

R&D Engineer

Founder of GoLinuxCloud with more than 15 years of expertise in Linux, Python, Go, Laravel, DevOps, Kubernetes, Git, Shell scripting, OpenShift, AWS, Networking, and Security. With extensive …