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
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:
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¶
- Use parentheses for mixed operators -- don't rely on precedence for readability:
(a & mask) >> shift. - Leverage short-circuit for guards --
if p != nil && p.Valid()is safe and idiomatic. - Use compound assignment --
x += 5is preferred overx = x + 5. - Prefer bitwise for flag sets -- combine
iotawith bit shifts for permission/feature flags. - Keep expressions simple -- break complex expressions into named intermediate variables.
Common Pitfalls¶
Integer division truncation
Always cast to float before dividing when you need a fractional result.++ and -- are not expressions
Comparing different numeric types
Floating-point comparison
Performance Considerations¶
- Bit shifts vs multiplication:
x << 1is equivalent tox * 2but 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 / 8tox >> 3for unsigned integers. Write the clearer form.
Key Takeaways¶
- Go has no ternary operator, no prefix
++/--, and no operator overloading. ++and--are statements, not expressions -- they can't appear in assignments or arguments.- Logical operators short-circuit -- use this for nil guards and performance.
&^(bit clear) is Go-specific and useful for flag manipulation.- All comparisons require same-type operands -- no implicit numeric promotion.
- Integer division truncates -- cast to float first for fractional results.
- Slices, maps, and functions are not comparable (except to
nil).