Skip to content

Operators & Expressions Beginner

Go has a deliberately small operator set -- no ternary, no operator overloading, no prefix increment. This simplicity makes Go code predictable and easy to read across teams.


Arithmetic Operators

a, b := 10, 3

fmt.Println(a + b)   // 13   addition
fmt.Println(a - b)   // 7    subtraction
fmt.Println(a * b)   // 30   multiplication
fmt.Println(a / b)   // 3    integer division (truncates)
fmt.Println(a % b)   // 1    modulus (remainder)

Integer division truncates

fmt.Println(7 / 2)     // 3, not 3.5
fmt.Println(7.0 / 2.0) // 3.5 (float division)

Increment and Decrement

Go only supports postfix ++ and --, and they are statements, not expressions.

x := 5
x++           // x is now 6
x--           // x is now 5

// y := x++   // COMPILE ERROR: x++ is a statement, not an expression
// ++x        // COMPILE ERROR: no prefix increment

Comparison Operators

All comparisons return bool. Both operands must be the same type.

a, b := 10, 20

fmt.Println(a == b)  // false   equal
fmt.Println(a != b)  // true    not equal
fmt.Println(a < b)   // true    less than
fmt.Println(a > b)   // false   greater than
fmt.Println(a <= b)  // true    less than or equal
fmt.Println(a >= b)  // false   greater than or equal

Comparable Types

Comparable Types
Yes bool, all numeric types, string, pointers, channels, arrays (if elements are comparable), structs (if all fields are comparable), interfaces
No slices, maps, functions (can only compare to nil)
// Struct comparison -- all fields must be comparable
type Point struct{ X, Y int }
p1 := Point{1, 2}
p2 := Point{1, 2}
fmt.Println(p1 == p2) // true

// Slice comparison -- compile error
// s1, s2 := []int{1}, []int{1}
// fmt.Println(s1 == s2) // COMPILE ERROR

Logical Operators

a, b := true, false

fmt.Println(a && b)  // false   AND
fmt.Println(a || b)  // true    OR
fmt.Println(!a)      // false   NOT

Short-Circuit Evaluation

Go short-circuits logical operators: the right operand is not evaluated if the left determines the result.

func expensive() bool {
    fmt.Println("called")
    return true
}

false && expensive() // expensive() is NEVER called
true || expensive()  // expensive() is NEVER called

This is commonly used for nil guard patterns:

if user != nil && user.IsActive() {
    // safe: IsActive() only called when user is not nil
}

Bitwise Operators

a, b := uint8(0b1100), uint8(0b1010)

fmt.Printf("%08b\n", a & b)   // 00001000  AND
fmt.Printf("%08b\n", a | b)   // 00001110  OR
fmt.Printf("%08b\n", a ^ b)   // 00000110  XOR
fmt.Printf("%08b\n", a &^ b)  // 00000100  AND NOT (bit clear)
fmt.Printf("%08b\n", a << 2)  // 00110000  left shift
fmt.Printf("%08b\n", a >> 1)  // 00000110  right shift

&^ -- Bit Clear (AND NOT)

This is unique to Go. It clears the bits in the left operand that are set in the right operand.

// Clear the write permission
const (
    Read    = 1 << iota // 1
    Write               // 2
    Execute             // 4
)

perms := Read | Write | Execute // 7 (0b111)
perms &^= Write                // 5 (0b101) -- write cleared

Assignment Operators

x := 10
x += 5    // x = x + 5  → 15
x -= 3    // x = x - 3  → 12
x *= 2    // x = x * 2  → 24
x /= 4    // x = x / 4  → 6
x %= 4    // x = x % 4  → 2
x <<= 2   // x = x << 2 → 8
x >>= 1   // x = x >> 1 → 4
x &= 0b11 // x = x & 3  → 0
x |= 0b10 // x = x | 2  → 2
x ^= 0xFF // x = x ^ 255

Address and Dereference Operators

x := 42
p := &x       // &x returns a pointer to x (*int)
fmt.Println(*p) // 42 -- *p dereferences the pointer
*p = 100
fmt.Println(x)  // 100 -- x changed through the pointer
Operator Name Usage
& Address-of p := &x -- get pointer to x
* Dereference *p -- get value at pointer
* Pointer type var p *int -- declare pointer to int

No Ternary Operator

Go intentionally omits the ternary operator (condition ? a : b). Use if/else instead.

// NOT valid Go:
// result := condition ? "yes" : "no"

// Idiomatic Go:
var result string
if condition {
    result = "yes"
} else {
    result = "no"
}

For simple cases, a helper function is common:

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

status := ternary(score >= 60, "pass", "fail")

Interview Tip

"Go has no ternary operator by design. The Go team chose explicit if/else over compact ternary because readability matters more than brevity. This is part of Go's philosophy of having one obvious way to do things."


Operator Precedence

From highest to lowest:

Precedence Operators
5 (highest) * / % << >> & &^
4 + - \| ^
3 == != < <= > >=
2 &&
1 (lowest) \|\|
// Precedence example
x := 2 + 3*4    // 14, not 20 (multiplication before addition)
y := 1<<2 + 1   // 5 (shift before addition: 4 + 1)

// Use parentheses for clarity
result := (a & mask) >> shift

Precedence tip

Bitwise operators (&, |, ^) bind tighter than comparison operators in Go, unlike C. This means x & 0xFF == 0 is parsed as x & (0xFF == 0) in C, but Go handles it more intuitively -- though you should still use parentheses for clarity.


Quick Reference

Category Operators Notes
Arithmetic + - * / % Integer / truncates
Comparison == != < > <= >= Returns bool; same-type only
Logical && \|\| ! Short-circuit evaluation
Bitwise & \| ^ &^ << >> &^ is Go-specific bit clear
Assignment = := += -= *= /= %= <<= >>= &= \|= ^= := is short declaration
Address & * Address-of and dereference
Increment x++ x-- Statements, not expressions; postfix only
Ternary None Use if/else

Best Practices

  1. Use parentheses for mixed operators -- don't rely on precedence for readability: (a & mask) >> shift.
  2. Leverage short-circuit for guards -- if p != nil && p.Valid() is safe and idiomatic.
  3. Use compound assignment -- x += 5 is preferred over x = x + 5.
  4. Prefer bitwise for flag sets -- combine iota with bit shifts for permission/feature flags.
  5. Keep expressions simple -- break complex expressions into named intermediate variables.

Common Pitfalls

Integer division truncation

percentage := 3 / 4           // 0, not 0.75
percentage := float64(3) / 4  // 0.75
Always cast to float before dividing when you need a fractional result.

++ and -- are not expressions

// All of these are COMPILE ERRORS:
y := x++      // can't use as expression
if x++ > 5 {} // can't use as expression
f(x++)        // can't use as expression
++x           // no prefix form

Comparing different numeric types

var a int32 = 10
var b int64 = 10
// fmt.Println(a == b) // COMPILE ERROR: mismatched types
fmt.Println(int64(a) == b) // true

Floating-point comparison

fmt.Println(0.1 + 0.2 == 0.3) // false (IEEE 754)

// Use epsilon comparison instead
const epsilon = 1e-9
result := math.Abs((0.1 + 0.2) - 0.3) < epsilon // true

Performance Considerations

  • Bit shifts vs multiplication: x << 1 is equivalent to x * 2 but the compiler optimizes both identically. Prefer readable code (x * 2) unless you're doing bitwise logic.
  • Short-circuit evaluation: Place cheap checks before expensive ones in && chains: if len(s) > 0 && expensiveValidation(s).
  • String comparison: String == compares length first, then bytes. It's O(n) but fast for different-length strings (O(1) short-circuit on length).
  • Division by power of 2: The compiler automatically converts x / 8 to x >> 3 for unsigned integers. Write the clearer form.

Key Takeaways

  1. Go has no ternary operator, no prefix ++/--, and no operator overloading.
  2. ++ and -- are statements, not expressions -- they can't appear in assignments or arguments.
  3. Logical operators short-circuit -- use this for nil guards and performance.
  4. &^ (bit clear) is Go-specific and useful for flag manipulation.
  5. All comparisons require same-type operands -- no implicit numeric promotion.
  6. Integer division truncates -- cast to float first for fractional results.
  7. Slices, maps, and functions are not comparable (except to nil).