Slices

By Syncfusion®, Inc. · Last updated

Go slices: growable lists built on arrays. Create them, append, copy, len and cap, the underlying array, slicing, removing and inserting, and arrays vs slices.

What Are Slices?

Slice = a list of values of one type that can grow and shrink.

  • It looks like an array, but without a size in the brackets: []int.
  • You can add items with append.
  • Behind every slice, Go keeps an array to hold the values.

Slices are the list you will use most in Go.

Simple lesson: a slice is a flexible list that grows when you add items.

A Real-World Example

Think of the class library's issue register.

  • On Monday, 3 students borrow books. The list has 3 names.
  • On Tuesday, 2 more borrow. The list grows to 5.
  • When the page is full, the librarian moves to a bigger page and copies the names across.

Now the mapping:

  • The list of names = the slice.
  • How many names are written = the slice's length.
  • How many lines the page has = the slice's capacity.
  • Moving to a bigger page = Go making a new, bigger array when the old one is full.

You met arrays in Arrays.

  • An array has a fixed size: [5]int.
  • A slice can grow: []int.
  • A slice is a small window onto an array.

What You Will Learn

In this lesson, you will learn:

  1. What slices are, why they are used, and how to create them.
  2. How to use append, copy, len and cap.
  3. What length, capacity and the underlying array are.
  4. How to slice, reslice, remove and insert elements.
  5. The differences between arrays and slices, and when to use each.

Slice Fundamentals

Why Slices Are Used

  • The size is not known in advance: students joining a club, marks typed in.
  • The list grows while the program runs.
  • Functions can take a slice of any length.
  • Most Go packages give and take slices, like strings.Split.

Slice Literals

Slice literal = a slice written out with its values.

A slice literal

package main

import "fmt"

func main() {
	marks := []int{91, 78, 85}
	names := []string{"Asha", "Ravi"}
	fmt.Println(marks, len(marks))
	fmt.Println(names, len(names))
}
[91 78 85] 3
[Asha Ravi] 2
  • []int has no number inside the brackets. That makes it a slice.
  • [3]int would be an array.

Creating Slices

Four ways to make a slice

make and nil slices

package main

import "fmt"

func main() {
	a := make([]int, 3)
	b := make([]int, 0, 10)
	var c []int
	fmt.Println(a, len(a), cap(a))
	fmt.Println(b, len(b), cap(b))
	fmt.Println(c, len(c), c == nil)
}
[0 0 0] 3 3
[] 0 10
[] 0 true
  • make([]int, 3) gives three zeros, ready to use.
  • make([]int, 0, 10) is empty, but has room for 10 without growing.
  • A nil slice prints as [] and has length 0.

Simple lesson: write []T{...} or use make to create a slice. With no number in the brackets, it is a slice, not an array.

Working with Slices

append()

append = add values to the end of a slice. It returns the new slice.

append

package main

import "fmt"

func main() {
	var club []string
	club = append(club, "Asha")
	club = append(club, "Ravi", "Meena")
	more := []string{"Kabir", "Priya"}
	club = append(club, more...)
	fmt.Println(club, len(club))
}
[Asha Ravi Meena Kabir Priya] 5
  • Always write club = append(club, ...). The result must be stored.
  • You can add many values at once.
  • more... adds every element of another slice.

len() and cap()

  • len(s) = how many elements the slice has now.
  • cap(s) = how many elements fit in the array behind it, before Go must make a bigger one.

Press append again and again. Watch the blue boxes and the dashed ones.

Look at what happens when the slice is full:

  • len = cap: there is no room.
  • Go makes a new array, usually about twice as big, and copies the values.
  • So cap jumps: 1, 2, 4, 8, 16.

Watch the capacity grow

package main

import "fmt"

func main() {
	var s []int
	for i := 1; i <= 5; i++ {
		s = append(s, i*10)
		fmt.Println(s, "len", len(s), "cap", cap(s))
	}
}
[10] len 1 cap 1
[10 20] len 2 cap 2
[10 20 30] len 3 cap 4
[10 20 30 40] len 4 cap 4
[10 20 30 40 50] len 5 cap 8
  • The exact growth is chosen by Go, and it may change between versions.
  • Do not write code that depends on the exact capacity.

append on a nil slice is fine: it makes the array for you.

But reading s[0] on an empty or nil slice is a panic. Check len(s) > 0 first.

copy()

copy = copy elements from one slice into another. It returns how many it copied.

copy

package main

import "fmt"

func main() {
	original := []int{91, 78, 85}
	backup := make([]int, len(original))
	n := copy(backup, original)
	backup[0] = 0
	fmt.Println(original, backup, n)
}
[91 78 85] [0 78 85] 3
  • copy(destination, source): the target comes first.
  • It copies only as many as fit in the smaller slice.
  • backup has its own array now, so changing it does not change original.

Simple lesson: append adds and returns a new slice, copy makes a separate copy, and len and cap tell you size and room.

Slice Internals

What a Slice Really Is

A slice is a small header with three parts:

  • pointer = where its first element is in the array
  • length = how many elements you can use
  • capacity = how many elements fit, from the start to the end of the array

Underlying Array

Underlying array = the array that really holds a slice's values.

Two slices can share one underlying array. Move the sliders, then press part[0]++.

Look at two things:

  • The blue boxes are the part of the array that part can see.
  • Changing part[0] changes the same box in nums.

Two slices share one array

package main

import "fmt"

func main() {
	nums := []int{10, 20, 30, 40, 50}
	part := nums[1:3]
	part[0] = 99
	fmt.Println(nums)
	fmt.Println(part, len(part), cap(part))
}
[10 99 30 40 50]
[99 30] 2 4
  • part starts at nums[1], so part[0] is the same box as nums[1].
  • Its capacity is 4: from nums[1] to the end of the array.

When append Surprises You

append can write into a shared array

  • When a slice shares an array and has spare capacity, append writes into that array.
  • To keep them apart, make a real copy first with copy.

Simple lesson: a slice is a window onto an array, and slices that share an array share their changes.

Slice Operations

Slicing

Slicing = taking part of a slice or array: s[low:high].

  • low is included. high is not.
  • s[:3] = from the start up to 3.
  • s[2:] = from 2 to the end.
  • s[:] = all of it.

Slicing

package main

import "fmt"

func main() {
	days := []string{"Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}
	fmt.Println(days[1:4])
	fmt.Println(days[:2])
	fmt.Println(days[4:])
}
[Tue Wed Thu]
[Mon Tue]
[Fri Sat]

Reslicing

Reslicing = slicing a slice again. It is fast, because no values are copied.

Reslice to drop the first and last

package main

import "fmt"

func main() {
	queue := []string{"Asha", "Ravi", "Meena", "Kabir"}
	queue = queue[1:]
	fmt.Println(queue)
	queue = queue[:len(queue)-1]
	fmt.Println(queue)
}
[Ravi Meena Kabir]
[Ravi Meena]
  • queue[1:] drops the first element, like serving the first person in a line.
  • queue[:len(queue)-1] drops the last element.

Removing Elements

To remove the element at index i, join the parts before and after it.

Remove an element

package main

import "fmt"

func main() {
	names := []string{"Asha", "Ravi", "Meena", "Kabir"}
	i := 1
	names = append(names[:i], names[i+1:]...)
	fmt.Println(names, len(names))
}
[Asha Meena Kabir] 3
  • names[:i] = everything before index 1.
  • names[i+1:]... = everything after it.
  • append joins them, so "Ravi" is gone.

Inserting Elements

To put a value at index i, make room and move the rest right.

Insert an element

package main

import "fmt"

func main() {
	line := []string{"Asha", "Meena", "Kabir"}
	i := 1
	line = append(line, "")
	copy(line[i+1:], line[i:])
	line[i] = "Ravi"
	fmt.Println(line)
}
[Asha Ravi Meena Kabir]
  • append(line, "") adds one empty slot at the end.
  • copy moves everything from i one step to the right.
  • Then we put "Ravi" into the free slot.

Easy way to remember

  • Remove at i: s = append(s[:i], s[i+1:]...)
  • Insert at i: grow by one, copy(s[i+1:], s[i:]), then s[i] = v
  • Go 1.21+ also has slices.Delete and slices.Insert. This lab does not run the slices package.

Simple lesson: s[low:high] takes a part, and append with slicing removes or inserts elements.

Arrays vs Slices

Differences

ArraySlice
Written as[5]int[]int
SizeFixed, part of the typeCan grow with append
Assigning b := aCopies every elementCopies the header. Both share the array
Passing to a functionThe function gets a copyThe function can change the elements
Compare with ==YesNo, only with nil
Zero valueFull of zero valuesnil

Memory Behavior

A function can change a slice's elements

package main

import "fmt"

func addGrace(marks []int) {
	for i := range marks {
		marks[i] += 5
	}
}

func main() {
	marks := []int{40, 60, 80}
	addGrace(marks)
	fmt.Println(marks)
}
[45 65 85]
  • The function gets a copy of the header, but both headers point to the same array.
  • So the changes are seen by the caller.
  • With an array, the function would change only its own copy. See Arrays.

When to Use Each

  • Use a slice almost always: lists of students, marks, words, lines of a file.
  • Use an array when the size is truly fixed and small: the 7 days of a week, an RGB colour [3]uint8.
  • Use an array when you need to compare with == or use it as a map key.

Simple lesson: use slices for lists that change, and arrays only for small, fixed-size groups.

TL;DR

  1. Slice = a growable list: []int. An array holds its values.
  2. Create = []int{1, 2}, make([]int, len, cap), var s []int (nil), arr[1:3].
  3. append = s = append(s, v). Always store the result.

    • append(s, other...) adds a whole slice.
  4. len = elements in use. cap = room in the array.

    • Full → Go makes a bigger array (often double).
  5. copy(dst, src) = a separate copy.
  6. Slicing = s[low:high], low included, high not. It shares the array.
  7. Remove = append(s[:i], s[i+1:]...). Insert = grow, copy, set.
  8. Array vs slice = fixed and copied vs growable and shared.

Common mistakes

MistakeWrong ideaCorrect ideaWhat goes wrong
append(s, 5) without s =append changes s in placeIt returns the new sliceThe new value is lost.
b := a to make a backupIt copies the valuesIt shares the arrayChanging b changes a. Use copy.
s[len(s)]The last index is lenIt is len - 1index out of range
s[2:5] on a 4-element sliceSlicing past the end is finehigh must be ≤ capslice bounds out of range
Writing to make([]int, 0, 5)[0]cap gives usable boxesOnly len boxes are usableindex out of range
a == b for two slicesSlices compare like arraysOnly == nil is allowedA build error.
Depending on exact cap growthGrowth is always ×2Go chooses, and it may changeCode breaks on another version.

Check Yourself

Exercises

What to Remember

  • A slice is a growable list: []T. An array holds its values.
  • Always write s = append(s, ...).
  • len is what you use. cap is the room before Go must grow the array.
  • s[low:high] shares the array. Use copy for a separate copy.
  • Remove with append(s[:i], s[i+1:]...).
  • Use slices for most lists. Use arrays only for small, fixed groups.

Next: Maps. You will learn how to store values by a key, like marks by student name.