cmp.Or Evaluation Semantics vs. Inline If-Else Chains in Go

Unlike inline if-else chains, cmp.Or evaluates all arguments eagerly before returning the first non-zero value, meaning side-effects in later arguments always execute regardless of earlier results.

Understanding the distinction between cmp.Or evaluation semantics and traditional control flow is critical for writing efficient Go code. The jetbrains/go-modern-guidelines repository explicitly documents this behavioral difference, noting that while cmp.Or offers conciseness for fallback chains, it fundamentally differs from short-circuiting if-else logic in how it processes arguments.

Eager vs. Lazy Evaluation: The Core Semantic Difference

The primary distinction lies in when arguments are computed. Inline if-else chains use lazy evaluation (short-circuiting): they stop evaluating as soon as a non-zero value is found, never touching subsequent branches. In contrast, cmp.Or uses eager evaluation: every argument is computed before the function is called, regardless of which value will ultimately be returned.

As implemented in the jetbrains/go-modern-guidelines repository, the FEATURES.md file explicitly warns that "all arguments are evaluated before the call" (line 966). This means cmp.Or(a, b, c) will execute the logic for a, b, and c unconditionally, whereas an if-else equivalent would only evaluate b if a was zero, and c only if both predecessors were zero.

Side Effects and Performance Implications

Because cmp.Or lacks short-circuiting, any function calls or expressions passed as arguments will always run. This creates a significant semantic and performance divergence from if-else chains.

Consider this side-effecting example:

func getEnv() string {
    fmt.Println("Reading env")
    return os.Getenv("CONFIG_PATH")
}

func getDefault() string {
    fmt.Println("Computing default")
    return defaultConfigPath()
}

Using an inline if-else chain provides lazy evaluation and controlled side effects:

// Inline if-else chain (short-circuiting)
func demoChain() string {
    if v := getEnv(); v != "" {
        return v  // Only "Reading env" is printed
    }
    return getDefault()
}

Using cmp.Or triggers eager evaluation of all arguments:

import "golang.org/x/exp/constraints"

// cmp.Or (eager evaluation)
func demoOr() string {
    // Both "Reading env" AND "Computing default" are printed
    return cmp.Or(getEnv(), getDefault())
}

Running demoChain() prints only "Reading env" (stopping if the environment variable exists), while demoOr() prints both lines even when the first value is non-empty. For expensive computations or operations with external side effects (logging, database queries, network calls), this eager evaluation can introduce unnecessary overhead.

Guidelines from the jetbrains/go-modern-guidelines Repository

The repository provides specific guidance on when to apply each pattern. According to README.md (line 7), cmp.Or(a, b, c) is recommended as a concise alternative "instead of a chain of nil checks," emphasizing its utility for simple value fallbacks. However, the FEATURES.md documentation (line 966) explicitly cautions developers about the eager evaluation semantics to prevent accidental side-effect execution.

The guidelines_test.go file contains validation logic ensuring that cmp_or appears correctly in the generated guidelines list, confirming this distinction is treated as a first-class concern within the repository's recommendations.

When to Use Each Approach

Choose between these patterns based on argument purity and computational cost:

  • cmp.Or – Use for pure values, constants, or cheap computations without side effects. Ideal for selecting the first non-zero string from environment variables, configuration defaults, or hardcoded fallbacks where evaluation order does not matter.

  • Inline if-else chains – Use when arguments involve expensive operations, I/O, or side effects. The lazy evaluation ensures you only pay for the computation you actually need, and it provides explicit control over when functions execute.

Summary

  • cmp.Or uses eager evaluation, computing all arguments before the function call, while inline if-else chains use lazy evaluation with short-circuiting.
  • Side effects always occur with cmp.Or arguments, unlike if-else chains where only the taken branch executes.
  • Performance characteristics differ significantly when arguments involve expensive computations; if-else chains avoid unnecessary work.
  • The jetbrains/go-modern-guidelines repository endorses cmp.Or for concise nil-check chains but explicitly documents its evaluation semantics in FEATURES.md (line 966).

Frequently Asked Questions

What is the main difference between cmp.Or and an if-else chain?

cmp.Or evaluates all arguments eagerly before the function call, returning the first non-zero value found, whereas inline if-else chains use lazy evaluation, stopping at the first non-zero value and never executing subsequent branches. This semantic difference affects both performance and side-effect behavior.

Does cmp.Or short-circuit like logical OR operators?

No. According to the jetbrains/go-modern-guidelines repository's FEATURES.md (line 966), all arguments are evaluated before the call returns, meaning there is no short-circuiting behavior. This contrasts sharply with the || operator or if-else statements, which halt evaluation immediately upon finding a truthy value.

Can I safely use cmp.Or with functions that have side effects?

Only if you intend for all side effects to occur. Because cmp.Or evaluates every argument unconditionally, any function calls with side effects will execute regardless of which value is ultimately returned. For operations involving logging, database queries, or state modifications, traditional if-else chains provide the necessary control to avoid unwanted execution.

When should I use cmp.Or over explicit nil checks?

The README.md (line 7) in the jetbrains/go-modern-guidelines repository recommends cmp.Or specifically for concise fallback chains when working with pure values or cheap computations. It replaces verbose if-else nil checks with a single expression, but should be avoided when arguments involve costly work or side effects that require conditional execution.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →