Go’s conditionals and loops are minimalist by design, but their simplicity hides precise memory decisions. Let’s explore how if
, switch
, and for
interact with the stack, heap, and data segments—and where hidden allocations creep in.
The Code
package main
func main() {
// Conditional with stack-allocated variable
if score := 42; score > 50 {
// ...
}
// Loop with slice iteration
nums := []int{10, 20, 30} // Slice header (stack), backing array (heap)
for i, n := range nums {
// ...
}
// Loop closure capturing variable
for _, n := range nums {
func() {
println(n) // n escapes to heap
}()
}
}
Memory Segmentation Visualized
+-------------------+ Lowest Address
| Code |
|-------------------|
| main() | // Compiled logic for conditionals/loops
| range handling | // Machine code for slice iteration
+-------------------+
| Data |
|-------------------|
| (No constants) |
+-------------------+
| Stack |
|-------------------|
| score (int = 42) | // if-scoped variable
| nums (slice) | // Pointer to heap array
| i, n (loop vars) | // Per-iteration copies
+-------------------+
| Heap |
|-------------------|
| []int backing | // nums' array [10, 20, 30]
| n (int copies) | // Captured by closure (one per iteration)
+-------------------+ Highest Address
Breakdown of Memory Behavior
1. Conditionals (if
, switch
)
-
Variables declared in
if
/switch
:-
score
inif score := 42; ...
is stack-allocated. - Scoped to the block, cleaned up immediately.
-
- No implicit heap allocations unless variables escape (e.g., passed to a closure).
2. Loops (for
, range
)
-
Loop variables:
-
i
andn
infor i, n := range nums
are reused per iteration (stack-allocated). - Values are copied, so modifying
n
doesn’t affect the original slice.
-
-
Slice backing array:
-
nums := []int{10, 20, 30}
stores the slice header (pointer, len, cap) on the stack. - The actual array
[10, 20, 30]
lives on the heap.
-
3. Closures in Loops
-
Captured variables:
-
n
in the closurefunc() { println(n) }()
escapes to the heap. - Each iteration’s
n
is copied to the heap to outlive the loop iteration.
-
- Verify with escape analysis:
go build -gcflags="-m" main.go
# ./main.go:12:3: ... n escapes to heap
Why This Matters
-
Loop variable reuse: Go reuses the same memory address for
i
andn
in each iteration.-
Gotcha: Goroutines in loops capturing loop variables (e.g.,
go func() { ... }()
) will capture the latest value ofi
/n
. Fix:
for _, n := range nums { n := n // Create a stack copy per iteration go func() { println(n) }() }
-
Gotcha: Goroutines in loops capturing loop variables (e.g.,
Slice efficiency: Large slices force heap allocations for backing arrays. Pre-size with
make
to avoid reallocations.
Optimization Tips
- Avoid closures in hot loops: Use explicit parameters to keep variables on the stack.
- Pre-allocate slices:
data := make([]int, 0, 1000) // Heap-allocated but avoids reallocations
- Beware of interface{} in conditions:
var val interface{} = 42 // Heap escape
if val == 42 { ... } // Type check overhead
Key Insight: Go’s for
loops reuse variables to minimize allocations—a design choice that’s fast but demands caution with closures and goroutines.