Go Slice Operations: The Magic Backpack Adventure 🎒
Imagine you have a magic backpack. Unlike a regular backpack with fixed pockets, this one can grow, shrink, and even share its contents with other backpacks. That’s exactly what a slice is in Go!
Today, we’ll explore five superpowers of this magic backpack:
- Append – Adding more stuff
- Copy – Making duplicates
- Slice Expressions – Taking just what you need
- nil vs Empty – The difference between “no backpack” and “empty backpack”
- Reallocation – When the backpack upgrades itself
1. Slice Append: Growing Your Backpack
The Story
You’re packing for a trip. You start with 3 toys. Then your friend gives you 2 more. Your magic backpack just… grows!
How It Works
toys := []string{"car", "ball", "doll"}
toys = append(toys, "robot", "puzzle")
// toys is now: [car ball doll robot puzzle]
Key Points:
append()adds items to the end- Always reassign the result:
toys = append(toys, ...) - Can add one or many items at once
Visual Flow
graph TD A["toys: car, ball, doll"] --> B["append robot, puzzle"] B --> C["toys: car, ball, doll, robot, puzzle"]
Appending One Slice to Another
Want to combine two backpacks?
bag1 := []int{1, 2, 3}
bag2 := []int{4, 5}
bag1 = append(bag1, bag2...)
// bag1 is now: [1 2 3 4 5]
The ... unpacks the second slice. Think of it like dumping everything from bag2 into bag1.
2. Slice Copy: Making a Twin Backpack
The Story
Your friend wants the same toys you have. You don’t want to share the same backpack (because if they lose a toy, you lose it too!). So you copy everything into their own backpack.
How It Works
original := []int{10, 20, 30}
twin := make([]int, len(original))
copied := copy(twin, original)
// twin is now: [10 20 30]
// copied = 3 (number of items copied)
Why Use Copy?
- Creates an independent clone
- Changes to
twinwon’t affectoriginal
The Trap: Size Matters!
small := make([]int, 2)
big := []int{1, 2, 3, 4, 5}
n := copy(small, big)
// small = [1, 2]
// n = 2 (only 2 items fit!)
copy() only fills what fits. The destination must be big enough!
Visual Flow
graph TD A["original: 10, 20, 30"] --> B["copy to twin"] B --> C["twin: 10, 20, 30"] D["Changes to twin"] --> E["original stays same!"]
3. Slice Expressions: Taking a Piece
The Story
You have 10 toys, but you only want to play with toys 3 to 7. You reach into the backpack and pull out just that section!
Basic Slicing
nums := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
part := nums[3:7]
// part = [3 4 5 6]
The Rule: slice[start:end] gives items from index start up to (but NOT including) end.
Quick Reference
| Expression | Result | Meaning |
|---|---|---|
nums[2:5] |
[2 3 4] |
Index 2 to 4 |
nums[:4] |
[0 1 2 3] |
Start to index 3 |
nums[6:] |
[6 7 8 9] |
Index 6 to end |
nums[:] |
full copy | Everything! |
The Warning: Shared Memory!
original := []int{1, 2, 3, 4, 5}
piece := original[1:4] // [2 3 4]
piece[0] = 999
// original is now [1 999 3 4 5]!
Slices share the same underlying storage. Changing piece changes original too!
Full Slice Expression (Three-Index)
Want to limit the capacity?
data := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
limited := data[2:5:6]
// limited = [2 3 4]
// len = 3, cap = 4
Format: slice[start:end:maxCap]
This prevents the new slice from seeing beyond index 6.
4. nil and Empty Slices: No Backpack vs Empty Backpack
The Story
nil slice = You don’t even own a backpack yet. Empty slice = You have a backpack, but it’s empty.
Both have no toys, but they’re different!
How They Look
var nilSlice []int // nil slice
emptySlice := []int{} // empty slice
alsoEmpty := make([]int, 0) // also empty
The Comparison
| Property | nil Slice | Empty Slice |
|---|---|---|
len() |
0 | 0 |
cap() |
0 | 0 |
== nil |
true | false |
| Safe to append? | Yes! | Yes! |
Real Example
var items []int // nil
items = append(items, 1) // Works perfectly!
// items = [1]
Go is smart. Appending to nil automatically creates the slice for you.
When Does It Matter?
func process(data []int) {
if data == nil {
fmt.Println("No data provided")
return
}
if len(data) == 0 {
fmt.Println("Empty list")
return
}
// Process data...
}
- Check
== nilwhen “nothing was given” - Check
len() == 0when “list exists but empty”
5. Slice Reallocation: The Auto-Upgrade
The Story
Your magic backpack has a capacity – how much it CAN hold. When you try to add more than it can hold, magic happens: the backpack transforms into a BIGGER one!
Understanding Capacity
pack := make([]int, 3, 5)
// Length: 3 (items inside)
// Capacity: 5 (total space)
graph TD A["pack"] --> B["len=3: items you have"] A --> C["cap=5: total space available"]
When Reallocation Happens
nums := make([]int, 2, 2) // len=2, cap=2
nums = append(nums, 3) // Need more space!
// Go creates NEW bigger slice
// Old: cap=2, New: cap=4 (doubled!)
The Rule: When len would exceed cap, Go:
- Creates a new, larger underlying array
- Copies all items
- Returns the new slice
Growth Pattern
| Old Capacity | New Capacity |
|---|---|
| 0-256 | doubles (Ă—2) |
| 256+ | grows by ~25% |
Why Does This Matter?
// Inefficient: many reallocations
var data []int
for i := 0; i < 10000; i++ {
data = append(data, i)
}
// Efficient: one allocation
data := make([]int, 0, 10000)
for i := 0; i < 10000; i++ {
data = append(data, i)
}
If you know the size ahead, use make() with capacity!
Detecting Reallocation
a := []int{1, 2, 3}
b := a[:]
a = append(a, 4, 5, 6, 7, 8)
// Did reallocation happen?
// Change a[0] and check b[0]
a[0] = 999
fmt.Println(b[0]) // Still 1? Yes = reallocated!
After reallocation, a and b no longer share memory.
Quick Summary
| Operation | What It Does | Remember |
|---|---|---|
append() |
Adds items | Always reassign! |
copy() |
Duplicates | Size of destination limits copy |
slice[a:b] |
Takes portion | Shares memory with original |
nil slice |
No slice exists | Safe to append |
| Empty slice | Slice exists, 0 items | != nil |
| Reallocation | Auto-grows slice | Happens when cap exceeded |
The Big Picture
graph TD A["Create Slice"] --> B{Need more space?} B -->|Yes| C["append - grows automatically"] B -->|No| D["Just add items"] C --> E{Exceeds capacity?} E -->|Yes| F["REALLOCATION!<br>New bigger array"] E -->|No| G["Uses existing space"] F --> H["Old slices now independent"]
You’ve Got This!
Slices are one of Go’s most powerful features. Now you understand:
- How to grow with
append() - How to clone with
copy() - How to slice with expressions
- The difference between nil and empty
- Why reallocation makes slices magical
Go build something awesome! 🚀
