Go Slice

A Go slice is a flexible view over an underlying array. Unlike an array, whose length is part of its type and cannot change, a slice can grow when values are appended. Slices are commonly used for collections whose number of elements is not known in advance.

A slice stores three pieces of information: a reference to an underlying array, its current length, and its capacity. This tutorial explains how to declare and initialize slices, access and update elements, work with length and capacity, append values, create subslices, copy data, and initialize slices of structs.

Declare a Go Slice

To declare a Go slice, use the var keyword followed by the variable name and []T, where T is the element type. Unlike an array declaration, a slice declaration does not specify a fixed length inside the brackets.

</>
Copy
 var slicename []T

Here, slicename is the variable used to refer to the slice. A slice declared without initialization has the zero value nil. Its length and capacity are both zero.

</>
Copy
package main

import "fmt"

func main() {
	var numbers []int

	fmt.Println(numbers == nil)
	fmt.Println(len(numbers))
	fmt.Println(cap(numbers))
}

Output

true
0
0

Initialize a Go Slice with Values

You can initialize a Go slice with a slice literal. The values inside the braces become the initial elements, and the slice length and capacity are initially equal to the number of supplied values.

example.go

</>
Copy
package main

import "fmt"

func main() {
	 var numbers []int
	 numbers = []int {5, 1, 9, 8, 4}
	 fmt.Println(numbers)
}

Output

[5 1 9 8 4]

You can also combine the declaration and initialization into a single statement.

</>
Copy
var numbers = []int {5, 1, 9, 8, 4}

Inside a function, short variable declaration provides a more concise form.

</>
Copy
numbers := []int {5, 1, 9, 8, 4}

An empty but non-nil slice can be initialized with an empty slice literal:

</>
Copy
numbers := []int{}

Both a nil slice and an empty non-nil slice have length zero and can be used with append. They differ when compared with nil and may be represented differently by some encoders.

Create a Go Slice with make, Length, and Capacity

Use the built-in make function when you want to create a slice with a specific length, and optionally a larger capacity. Elements included in the length are initialized to the zero value of the element type.

</>
Copy
// declaration and initialization
var numbers = make([]int, 5, 10)

// or

// short variable declaration
numbers2 := make([]int, 5, 10)

In make([]int, 5, 10), the slice has five accessible elements and enough underlying storage for up to ten elements before additional allocation may be required.

You can omit the capacity argument when the required capacity is the same as the length.

</>
Copy
numbers := make([]int, 5)

The length cannot be greater than the capacity. Therefore, make([]int, 10, 5) is invalid.

Access and Update Go Slice Elements by Index

Go slices use zero-based indexing. The first element is at index 0, the second at index 1, and the last element is at index len(slice)-1.

In the following example, the slice elements at indexes 2 and 3 are read.

example.go

</>
Copy
package main

import "fmt"

func main() {
	var numbers = []int {5, 8, 14, 7, 3}
	
	// access elements of slice using index
	num2 := numbers[2]
	num3 := numbers[3]
	
	fmt.Println("numbers[2] : ", num2)
	fmt.Println("numbers[3] : ", num3)
}

Output

numbers[2] :  14
numbers[3] :  7

You can assign a new value to an existing element by using its index.

</>
Copy
package main

import "fmt"

func main() {
	numbers := []int{5, 8, 14}
	numbers[1] = 20

	fmt.Println(numbers)
}

Output

[5 20 14]

Accessing an index outside the range from 0 through len(slice)-1 causes a runtime panic.

Go Slice Length and Capacity with len and cap

A slice has a length and a capacity. The length is the number of elements currently accessible through the slice. The capacity is the number of elements available from the slice’s first element to the end of its underlying array.

Use len(slice) to read the length and cap(slice) to read the capacity.

example.go

</>
Copy
package main

import "fmt"

func main() {
	// declaration and intialization
	var numbers = make([]int, 5, 10)
	
	fmt.Println("Size of slice: ", len(numbers))
	fmt.Println("Capacity of slice: ", cap(numbers))
}

Output

Size of slice:  5
Capacity of slice:  10

The word size is sometimes used informally for the number of elements in a slice. In Go code and documentation, length is the precise term, and it is returned by len.

Why Go Slice Capacity Matters

Capacity determines how many elements can be accommodated in the current underlying array. When append would make the length exceed the capacity, Go allocates another underlying array and copies the existing elements into it.

Providing an estimated capacity can reduce repeated allocations when you know approximately how many values will be appended. Capacity is an allocation hint rather than a limit on the final slice length.

</>
Copy
package main

import "fmt"

func main() {
	numbers := make([]int, 0, 3)

	for i := 1; i <= 4; i++ {
		numbers = append(numbers, i)
		fmt.Printf("values=%v length=%d capacity=%d\n", numbers, len(numbers), cap(numbers))
	}
}

The exact capacity chosen after growth is an implementation detail. Code should not depend on a particular capacity growth factor.

Append One or More Elements to a Go Slice

Use the built-in append function to add values to a slice. Because append may return a slice backed by a different array, assign its return value back to the slice variable.

example.go

</>
Copy
package main

import "fmt"

func main() {
	var numbers = []int {5, 8, 14, 7, 3}
	
	numbers = append(numbers, 58)
	
	fmt.Println(numbers)
}

Output

[5 8 14 7 3 58]

You can append multiple elements in one call.

</>
Copy
numbers = append(numbers, 21, 34, 55)

To append every element from another slice, use the variadic expansion operator ....

</>
Copy
package main

import "fmt"

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

	first = append(first, second...)
	fmt.Println(first)
}

Output

[1 2 3 4]

Create a Subslicе with Go Slice Expressions

A slice expression selects a range from an array or another slice. The expression values[low:high] includes the element at low and excludes the element at high.

</>
Copy
package main

import "fmt"

func main() {
	values := []int{10, 20, 30, 40, 50}
	middle := values[1:4]

	fmt.Println(middle)
	fmt.Println(len(middle))
	fmt.Println(cap(middle))
}

Output

[20 30 40]
3
4

The lower or upper bound can be omitted. For example, values[:3] selects from the beginning through index 2, while values[2:] selects from index 2 to the end.

A subslice usually shares its underlying array with the original slice. Updating an element through one slice can therefore be visible through the other.

</>
Copy
package main

import "fmt"

func main() {
	values := []int{10, 20, 30, 40}
	part := values[1:3]

	part[0] = 99

	fmt.Println(values)
	fmt.Println(part)
}

Output

[10 99 30 40]
[99 30]

Iterate Over Go Slice Elements

Use a for loop with range to process each slice element. The range expression returns the zero-based index and a copy of the value at that index.

</>
Copy
package main

import "fmt"

func main() {
	numbers := []int{5, 8, 14}

	for index, value := range numbers {
		fmt.Println(index, value)
	}
}

Output

0 5
1 8
2 14

Use the blank identifier when only the index or only the value is required.

</>
Copy
for _, value := range numbers {
	fmt.Println(value)
}

Copy Go Slice Elements into Independent Storage

Use the built-in copy function when the destination should have separate backing storage. It copies up to the smaller of the source length and destination length and returns the number of copied elements.

</>
Copy
package main

import "fmt"

func main() {
	source := []int{10, 20, 30}
	destination := make([]int, len(source))

	count := copy(destination, source)
	destination[0] = 99

	fmt.Println("copied:", count)
	fmt.Println("source:", source)
	fmt.Println("destination:", destination)
}

Output

copied: 3
source: [10 20 30]
destination: [99 20 30]

Another concise way to clone a slice is to append its elements to a newly created empty slice.

</>
Copy
clone := append([]int(nil), source...)

Initialize a Go Slice of Structs

A slice can store values of any single element type, including structs. Initialize a slice of structs with a composite literal.

</>
Copy
package main

import "fmt"

type Product struct {
	Name  string
	Stock int
}

func main() {
	products := []Product{
		{Name: "Keyboard", Stock: 12},
		{Name: "Mouse", Stock: 20},
	}

	fmt.Println(products[0].Name)
	fmt.Println(products[1].Stock)
}

Output

Keyboard
20

Use a slice of pointers, such as []*Product, only when pointer semantics are needed. A slice of values is often sufficient for small structs.

Remove Elements from a Go Slice

Go does not provide a dedicated remove function for slices. To remove the element at index i while preserving order, append the portion after the element to the portion before it.

</>
Copy
numbers = append(numbers[:i], numbers[i+1:]...)

The resulting slice may still reference the original underlying array. For slices that hold pointers or large objects, clear the unused final element before shortening the slice when retaining that reference would keep data alive unnecessarily.

</>
Copy
package main

import "fmt"

func main() {
	numbers := []int{10, 20, 30, 40}
	i := 1

	numbers = append(numbers[:i], numbers[i+1:]...)
	fmt.Println(numbers)
}

Output

[10 30 40]

Common Go Slice Mistakes

  • Forgetting to assign the result of append back to the slice variable.
  • Using an index equal to the slice length. The highest valid index is len(slice)-1.
  • Assuming a subslice owns independent data when it may share an underlying array with the original slice.
  • Creating a slice with a nonzero length when only capacity was intended. Use make([]T, 0, capacity) before repeated appends.
  • Depending on a specific capacity increase after append. Capacity growth is not a stable API guarantee.

Go Slice Frequently Asked Questions

What is the capacity of a slice in Go?

The capacity of a Go slice is the number of elements available from the slice’s first element to the end of its underlying array. It is returned by cap(slice). The capacity can be greater than the current length.

How do you initialize a slice in Go?

Use a slice literal such as numbers := []int{1, 2, 3} to initialize values directly. Use make([]int, length, capacity) when you need a specific initial length or reserved capacity.

What is the size of a Go slice?

The number of currently accessible elements is called the slice length and is returned by len(slice). A slice itself is a small descriptor that refers to an underlying array; len does not report its memory usage in bytes.

Are Go slices zero indexed?

Yes. The first element is at index 0, and the final valid index is len(slice)-1. Accessing an index outside that range causes a runtime panic.

What is the difference between a nil slice and an empty slice?

A nil slice has no underlying array reference and compares equal to nil. An empty initialized slice does not compare equal to nil. Both have zero length, can be ranged over, and can receive appended values.

Go Slice Editorial QA Checklist

  • Verify that slice length is described as the number of accessible elements and capacity as the remaining underlying-array range.
  • Confirm that every newly added Go code block uses the language-go class and every result-only block uses output.
  • Check that all index examples use zero-based indexing and avoid reading at index len(slice).
  • Ensure that examples assigning values returned by append store the returned slice.
  • Confirm that subslice examples explain shared backing arrays and that capacity growth is not presented as a fixed formula.

Go Slice Summary

In this Go Tutorial, we learned how to declare and initialize a Go slice, access and update elements, inspect its length and capacity, append values, create subslices, iterate with range, copy elements, initialize slices of structs, and remove elements.