Skip to main content

Slices

What Is a Slice?​

Slices are the most widely-used data structure in Go. Unlike fixed-length arrays, a slice is a dynamically-sized sequence whose length can grow at runtime.

Internally a slice is a three-field header:

Slice header (24 bytes on 64-bit)
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ ptr β”‚ len β”‚ cap β”‚
β”‚ (8 byte) β”‚(8 byte)β”‚ (8 byte)β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β”‚
β–Ό
β”Œβ”€β”€β”€β”€β”¬β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”
β”‚ 0 β”‚ 1 β”‚ 2 β”‚ 3 β”‚ 4 β”‚ ← backing array (heap-allocated)
β””β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”˜
  • ptrβ€” pointer to the first element of the backing array
  • lenβ€” number of elements currently in use
  • capβ€” total capacity of the backing array (max elements before reallocation)

Declaring and Initialising Slices​

package main

import "fmt"

func main() {
// 1. nil slice (len=0, cap=0, ptr=nil)
var s1 []int
fmt.Println(s1, len(s1), cap(s1)) // [] 0 0
fmt.Println(s1 == nil) // true

// 2. Literal initialisation
s2 := []int{1, 2, 3, 4, 5}
fmt.Println(s2, len(s2), cap(s2)) // [1 2 3 4 5] 5 5

// 3. make(type, len, cap) β€” cap defaults to len when omitted
s3 := make([]int, 3, 10)
fmt.Println(s3, len(s3), cap(s3)) // [0 0 0] 3 10

// 4. Slicing an array
arr := [5]int{10, 20, 30, 40, 50}
s4 := arr[1:4] // indices 1..3 (4 excluded)
fmt.Println(s4, len(s4), cap(s4)) // [20 30 40] 3 4
}

Slice Expressions​

The full form is s[low:high:max].

package main

import "fmt"

func main() {
s := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}

// s[low:high] β€” len=high-low, cap=len(s)-low
a := s[2:5]
fmt.Printf("a=%v len=%d cap=%d\n", a, len(a), cap(a))
// a=[2 3 4] len=3 cap=8

// s[low:high:max] β€” cap=max-low (three-index slice)
b := s[2:5:6]
fmt.Printf("b=%v len=%d cap=%d\n", b, len(b), cap(b))
// b=[2 3 4] len=3 cap=4

// Shorthand forms
fmt.Println(s[:3]) // [0 1 2] β€” same as s[0:3]
fmt.Println(s[7:]) // [7 8 9] β€” same as s[7:len(s)]
fmt.Println(s[:]) // [0 1 2 ... 9] β€” same as s[0:len(s)]
}

append and Automatic Reallocation​

append adds elements to a slice. When cap is exceeded, Go allocates a new backing array and copies existing data.

package main

import "fmt"

func main() {
s := make([]int, 0, 4)
fmt.Printf("initial: %v len=%d cap=%d ptr=%p\n", s, len(s), cap(s), s)

for i := 1; i <= 8; i++ {
prevCap := cap(s)
prevPtr := fmt.Sprintf("%p", s)
s = append(s, i)
if cap(s) != prevCap {
fmt.Printf("reallocated! cap %d→%d, ptr %s→%p\n", prevCap, cap(s), prevPtr, s)
}
}
fmt.Println(s)

// Appending another slice with ...
a := []int{1, 2, 3}
b := []int{4, 5, 6}
a = append(a, b...)
fmt.Println(a) // [1 2 3 4 5 6]
}

copy β€” Creating an Independent Copy​

copy(dst, src) copies min(len(dst), len(src)) elements and returns the count.

package main

import "fmt"

func main() {
src := []int{1, 2, 3, 4, 5}

// Simple assignment shares the backing array
shared := src
shared[0] = 999
fmt.Println(src) // [999 2 3 4 5] β€” original changed!

// copy creates an independent slice
src2 := []int{1, 2, 3, 4, 5}
dst := make([]int, len(src2))
n := copy(dst, src2)
dst[0] = 999
fmt.Println(src2) // [1 2 3 4 5] β€” original preserved
fmt.Println(dst) // [999 2 3 4 5]
fmt.Println(n) // 5 (elements copied)

// Partial copy
dst2 := make([]int, 3)
copy(dst2, src2[1:])
fmt.Println(dst2) // [2 3 4]
}

Shared Backing Array Pitfalls​

Simple assignment or slicing shares the backing array. Unintended mutations can be subtle.

package main

import "fmt"

func main() {
base := []int{1, 2, 3, 4, 5}

// Slicing shares the backing array
sub := base[1:4] // [2 3 4]
sub[0] = 200
fmt.Println(base) // [1 200 3 4 5] β€” base changed too!

// append beyond cap creates a new backing array
base2 := []int{1, 2, 3, 4, 5}
sub2 := base2[1:3:3] // len=2, cap=2 (cap limited by third index)
sub2 = append(sub2, 99) // cap exceeded β†’ new array allocated
sub2[0] = 200
fmt.Println(base2) // [1 2 3 4 5] β€” separated, no effect
fmt.Println(sub2) // [200 3 99]
}

Common Slice Patterns​

package main

import "fmt"

func main() {
// Stack
var stack []int
stack = append(stack, 1, 2, 3) // push
top := stack[len(stack)-1] // peek
stack = stack[:len(stack)-1] // pop
fmt.Println(top, stack) // 3 [1 2]

// Queue
var queue []int
queue = append(queue, 1, 2, 3) // enqueue
front := queue[0] // peek front
queue = queue[1:] // dequeue
fmt.Println(front, queue) // 1 [2 3]

// Filter β€” keep even numbers
nums := []int{1, 2, 3, 4, 5, 6, 7, 8}
var evens []int
for _, n := range nums {
if n%2 == 0 {
evens = append(evens, n)
}
}
fmt.Println(evens) // [2 4 6 8]

// Delete element at index i (order preserved)
s := []int{1, 2, 3, 4, 5}
i := 2
s = append(s[:i], s[i+1:]...)
fmt.Println(s) // [1 2 4 5]

// Delete element (order not preserved, faster)
s2 := []int{1, 2, 3, 4, 5}
s2[i] = s2[len(s2)-1]
s2 = s2[:len(s2)-1]
fmt.Println(s2) // [1 2 5 4]
}

2-D Slices​

package main

import "fmt"

func main() {
rows := 3
matrix := make([][]int, rows)
for i := range matrix {
matrix[i] = make([]int, i+1) // triangular shape
for j := range matrix[i] {
matrix[i][j] = (i+1) * (j+1)
}
}
for _, row := range matrix {
fmt.Println(row)
}
// [1]
// [2 4]
// [3 6 9]
}

Practical Example β€” Sliding Window Maximum​

package main

import "fmt"

// Returns the maximum in each sliding window of size k.
func maxSlidingWindow(nums []int, k int) []int {
if len(nums) == 0 || k == 0 {
return nil
}
result := make([]int, 0, len(nums)-k+1)
for i := 0; i <= len(nums)-k; i++ {
window := nums[i : i+k]
maxVal := window[0]
for _, v := range window[1:] {
if v > maxVal {
maxVal = v
}
}
result = append(result, maxVal)
}
return result
}

func main() {
nums := []int{1, 3, -1, -3, 5, 3, 6, 7}
fmt.Println(maxSlidingWindow(nums, 3))
// [3 3 5 5 6 7]
}