Go (Golang) Tutorial 0/45 lessons ~6 min read Lesson 13

    Slices

    Slices are Go's workhorse collection — dynamic views into underlying arrays with length and capacity.

    Course progress0%
    Focus
    10 guided sections
    Practice signal
    Examples included
    Career prep
    Interview Q&A included

    Introduction

    Slices are Go's workhorse collection — dynamic views into underlying arrays with length and capacity. The built-in append function grows slices, potentially reallocating. Understanding slice headers (pointer, len, cap) explains aliasing bugs that plague even experienced developers.

    Slices power JSON arrays, database result sets, HTTP middleware chains, and buffer management. Mastering slice expressions (s[low:high:max]), copy, and nil vs empty slice distinction is mandatory for production Go and interviews.

    This lesson includes Go 1.21+ slices package helpers (Contains, Equal, Compact) that replace hand-rolled loops in modern codebases.

    The story

    A Google Cloud Load Balancer health-check service maintains a dynamic list of backend endpoints. Slices grow as autoscaler events add pods and shrink when nodes drain — append amortizes allocation while [:0] resets length without freeing capacity, a trick used in high-throughput log aggregators processing millions of events per second.

    Understanding slice headers (pointer, length, capacity) explains subtle bugs when sharing subslices across goroutines in a microservice mesh.

    Understanding the topic

    Key concepts

    • Slice header: pointer to array, len, cap — s[i:j] creates sub-slice sharing backing array.
    • append(s, elems...) may reallocate if cap exceeded — returns new slice header.
    • nil slice vs empty slice: both len 0; nil has no backing array.
    • make([]T, len, cap) preallocates; len defaults elements to zero.
    • copy(dst, src) copies min(len(dst), len(src)) elements.
    • Full slice expression s[low:high:max] limits cap of result slice.
    text
    flowchart LR
    Array[Array fixed] --> Slice[Slice view]
    Slice --> Append[append grows]
    Append --> Cap[capacity doubles]

    Step-by-step explanation

    1. Literal: []int{1,2,3} creates slice with backing array.
    2. append adds elements; if len < cap, uses existing array; else doubles cap.
    3. Sub-slice shares array — mutation visible across slices.
    4. Pass slice to function — copies header only, not elements.
    5. Range gives index and copy of element (struct copy).
    6. slices.Clone (Go 1.21+) deep-copies slice elements.

    Practical code example

    Slice growth, sub-slicing, and safe copying:

    go
    package main
    import (
    "fmt"
    "slices"
    )
    func main() {
    s := make([]int, 0, 4)
    s = append(s, 1, 2, 3)
    fmt.Printf("s=%v len=%d cap=%d\n", s, len(s), cap(s))
    sub := s[1:3] // shares backing array
    sub[0] = 99
    fmt.Println("after sub mutate:", s)
    cloned := slices.Clone(s)
    cloned[0] = 0
    fmt.Println("original:", s, "clone:", cloned)
    fmt.Println("contains 99:", slices.Contains(s, 99))
    }

    Output

    s=[1 2 3] len=3 cap=4
    after sub mutate: [1 99 3]
    original: [1 99 3] clone: [0 99 3]
    contains 99: true

    Line-by-line code explanation

    • endpoints := make([]string, 0, 32) pre-allocates capacity to reduce reallocation.
    • endpoints = append(endpoints, podIP) grows the slice, copying when capacity is exceeded.
    • subset := endpoints[2:5] creates a subslice sharing the underlying array.
    • len(endpoints) is the number of elements; cap(endpoints) is underlying array size.
    • copy(dst, src) copies elements between slices without sharing references.
    • endpoints = endpoints[:0] resets length to zero while keeping allocated capacity for reuse.
    • slices.Delete(endpoints, i, i+1) (Go 1.21+) removes an element without manual shifting.
    • for i, ep := range endpoints iterates the current length, not capacity.

    Key takeaway: Always consider aliasing when sub-slicing. Use slices.Clone before mutating if independence needed. Preallocate with make when size known.

    Real-world use

    Where you'll use this in production

    • Building response DTO slices from database rows.
    • Buffer pooling in high-throughput HTTP servers.
    • Batch processing with sliding window sub-slices.
    • Collecting errors from parallel workers into []error.

    Best practices

    • Preallocate: make([]T, 0, expectedSize) when count known.
    • Use slices.Clone before mutating shared sub-slices.
    • Prefer slices.Contains, Equal, Compact over manual loops.
    • Return nil slice for 'no results' — idiomatic; json encodes as [].
    • Avoid append in tight loops without prealloc — causes repeated realloc.
    • Use full slice expression to prevent accidental cap extension.

    Common mistakes

    • Appending to slice while ranging over it — undefined behavior.
    • Sub-slice aliasing causing unexpected mutations.
    • Confusing nil and empty slice in JSON — both encode as [].
    • Not checking append return: s = append(s, x) — append may reallocate.
    • Holding pointer to slice element across append — invalid after realloc.

    Advanced interview questions

    Q1BeginnerSlice internals?
    Header with pointer to backing array, length, capacity — lightweight reference.
    Q2Beginnernil vs empty slice?
    Both len 0; nil has nil pointer; empty has backing array — usually interchangeable.
    Q3Intermediateappend reallocation behavior?
    If len+new <= cap, in place; else new array ~2x cap, copy elements, return new header.
    Q4IntermediatePrevent sub-slice aliasing bugs?
    slices.Clone, copy to new slice, or full slice expression limiting cap.
    Q5AdvancedDesign API returning slice — nil or empty?
    Return nil for none — idiomatic; document JSON behavior; avoid returning uninitialized make(0) unless needed.

    Summary

    Slices are dynamic views with pointer, len, and cap. append may reallocate — always assign result: s = append(s, x). Sub-slices share backing arrays — clone before independent mutation. Preallocate with make when size is predictable. Next lesson: maps — key-value associative stores.

    Ready to mark this lesson complete?Track your journey across the entire course.