DEV Community

Cover image for Slices in Go: A Deep Dive with Examples
Md Abu Musa
Md Abu Musa

Posted on

Slices in Go: A Deep Dive with Examples

When writing Go programs, you’ll find that arrays are often too rigid to be practical. That’s where slices come into play. Slices are Go’s built-in abstraction over arrays that offer flexibility, power, and performance.

In this blog, we’ll explore:

  • What slices are
  • How they work internally
  • How to create and use them
  • Common pitfalls
  • Best practices with examples

📌 What is a Slice?

A slice is a dynamically-sized, flexible view into the elements of an array. It doesn't store data itself — it points to an underlying array.

Slice Structure (Internally)

A slice in Go is a small struct with 3 fields:

type slice struct { ptr *T // pointer to the underlying array len int // number of elements in the slice cap int // total capacity (from ptr to end of array) } 
Enter fullscreen mode Exit fullscreen mode

✅ Creating Slices

1. From an Array

arr := [5]int{10, 20, 30, 40, 50} slice := arr[1:4] // [20 30 40] 
Enter fullscreen mode Exit fullscreen mode

Here:

  • slice references the array arr from index 1 to 3 (excluding index 4).
  • len(slice) = 3
  • cap(slice) = 4 (from index 1 to end of the array)

2. Using Slice Literal

numbers := []int{1, 2, 3} 
Enter fullscreen mode Exit fullscreen mode

This creates both a slice and its underlying array.

3. Using make()

s := make([]int, 3) // [0 0 0] s := make([]int, 3, 5) // len=3, cap=5 
Enter fullscreen mode Exit fullscreen mode

🔁 Slicing a Slice

data := []int{100, 200, 300, 400} sub := data[1:3] // [200 300] 
Enter fullscreen mode Exit fullscreen mode

This sub-slice shares memory with the original slice. Changing sub affects data:

sub[0] = 999 fmt.Println(data) // [100 999 300 400] 
Enter fullscreen mode Exit fullscreen mode

➕ Appending Elements

nums := []int{1, 2} nums = append(nums, 3, 4) // [1 2 3 4] 
Enter fullscreen mode Exit fullscreen mode

If the slice’s capacity is full, Go automatically allocates a new underlying array.


⚠️ Shared Memory Behavior

arr := []int{1, 2, 3, 4} s1 := arr[0:2] // [1 2] s2 := arr[1:3] // [2 3] s1[1] = 999 fmt.Println(arr) // [1 999 3 4] fmt.Println(s2) // [999 3] 
Enter fullscreen mode Exit fullscreen mode

Tip:

To avoid this, use copy():

original := []int{1, 2, 3} clone := make([]int, len(original)) copy(clone, original) 
Enter fullscreen mode Exit fullscreen mode

📈 Capacity Growth with append()

Go typically doubles the capacity when appending exceeds the current capacity.

s := make([]int, 0, 2) s = append(s, 1, 2, 3) fmt.Println(s) // [1 2 3] 
Enter fullscreen mode Exit fullscreen mode

If the capacity is exceeded, a new array is allocated behind the scenes.


📦 Built-in Functions

Function Description
len(slice) Returns number of elements
cap(slice) Returns total capacity
append(slice, elems...) Appends elements
copy(dst, src) Copies elements

🧪 Complete Example

func main() { original := []int{10, 20, 30, 40, 50} slice1 := original[1:4] fmt.Println("Slice1:", slice1) // [20 30 40] slice2 := append(slice1, 99) fmt.Println("Original:", original) fmt.Println("Slice2:", slice2) slice1[0] = 777 fmt.Println("Modified Slice1:", slice1) fmt.Println("Modified Original:", original) } 
Enter fullscreen mode Exit fullscreen mode

Output (May vary based on capacity reuse):

Slice1: [20 30 40] Original: [10 20 30 40 99] Slice2: [20 30 40 99] Modified Slice1: [777 30 40] Modified Original: [10 777 30 40 99] 
Enter fullscreen mode Exit fullscreen mode

❗ Common Mistakes

🔹 Assuming slices are independent:

a := []int{1, 2, 3} b := a b[0] = 999 fmt.Println(a) // [999 2 3] 
Enter fullscreen mode Exit fullscreen mode

Use copy() to truly clone a slice.

🔹 Forgetting that append() might create a new array

Always check whether your slice is still pointing to the original memory if you rely on shared behavior.


✅ Best Practices

  • Use slices for most collections — they’re more idiomatic and dynamic than arrays.
  • Use copy() when independence is needed.
  • Avoid unnecessary slicing inside performance-critical loops.
  • Use make([]T, len, cap) if you know the size ahead of time.

🧠 Summary

Feature Array Slice
Size Fixed Dynamic
Memory Sharing Yes Yes
Append Support No Yes
Preferred Use Rare Always

🔚 Final Thoughts

Slices are a cornerstone of Go’s design, combining performance with flexibility. Understanding how slices share memory with arrays, grow, and interact with built-in functions is essential for writing idiomatic and safe Go code.

Once you master slices, you’ll write cleaner, more efficient programs — and avoid some tricky bugs!

Top comments (0)