Go

How do you correctly allocate a 2D slice, and what are the performance trade-offs?

Question 52MediumGo 1.22 to 1.25

make([][]int, n) creates only the outer slice of n nil rows. Writing grid[0][0] panics until you allocate each row. There are two approaches.

// 1) Row-per-allocation: simple, rows can be resized independently
grid := make([][]int, rows)
for i := range grid {
	grid[i] = make([]int, cols)
}

// 2) Single backing allocation: 2 allocs total, contiguous, cache-friendly
backing := make([]int, rows*cols)
grid2 := make([][]int, rows)
for i := range grid2 {
	grid2[i] = backing[i*cols : (i+1)*cols : (i+1)*cols] // cap-limit each row!
}

// 3) Fixed size known at compile time: an array of arrays, zero heap allocs possible
var board [8][8]byte

In option 2 the three-index slice matters. Without it, append(grid2[0], x) would overwrite the first element of row 1.

Trade-offs:

  • Per-row allocation causes rows+1 allocations, more GC pressure, and poor locality.
  • A flat []int indexed by r*cols+c is fastest, and is what numeric libraries (gonum) use.
  • Arrays of arrays are values: copying board copies all 64 bytes, which is handy for game-state snapshots in search algorithms.

More on Arrays, Slices, Maps & Strings

All 37 Arrays, Slices, Maps & Strings questions