DEV Community

Cover image for Go Course: Arrays and Slices
Karan Pratap Singh
Karan Pratap Singh

Posted on • Originally published at karanpratapsingh.com

Go Course: Arrays and Slices

In this tutorial, we will learn about arrays and slices in Go.

Arrays

So what's an array?

An array is a fixed-size collection of elements of the same type. The elements of the array are stored sequentially and can be accessed using their index.

array

Declaration

We can declare an array as follows:

var a [n]T
Enter fullscreen mode Exit fullscreen mode

Here, n is the length and T can be any type like integer, string, or user-defined structs.

Now, let's declare an array of integers with length 4 and print it.

func main() {
    var arr [4]int

    fmt.Println(arr)
}
Enter fullscreen mode Exit fullscreen mode
$ go run main.go
[0 0 0 0]
Enter fullscreen mode Exit fullscreen mode

By default, all the array elements are initialized with the zero value of the corresponding array type.

Initialization

We can also initialize an array using an array literal.

var a [n]T = [n]T{V1, V2, ... Vn}
Enter fullscreen mode Exit fullscreen mode
func main() {
    var arr = [4]int{1, 2, 3, 4}

    fmt.Println(arr)
}
Enter fullscreen mode Exit fullscreen mode
$ go run main.go
[1 2 3 4]
Enter fullscreen mode Exit fullscreen mode

We can even do a shorthand declaration.

...
arr := [4]int{1, 2, 3, 4}
Enter fullscreen mode Exit fullscreen mode

Access

And similar to other languages, we can access the elements using the index as they're stored sequentially.

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

    fmt.Println(arr[0])
}
Enter fullscreen mode Exit fullscreen mode
$ go run main.go
1
Enter fullscreen mode Exit fullscreen mode

Iteration

Now, let's talk about iteration.

So, there are multiple ways to iterate over arrays.

The first one is using the for loop with the len function which gives us the length of the array.

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

    for i := 0; i < len(arr); i++ {
        fmt.Printf("Index: %d, Element: %d\n", i, arr[i])
    }
}
Enter fullscreen mode Exit fullscreen mode
$ go run main.go
Index: 0, Element: 1
Index: 1, Element: 2
Index: 2, Element: 3
Index: 3, Element: 4
Enter fullscreen mode Exit fullscreen mode

Another way is to use the range keyword with the for loop.

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

    for i, e := range arr {
        fmt.Printf("Index: %d, Element: %d\n", i, e)
    }
}
Enter fullscreen mode Exit fullscreen mode
$ go run main.go
Index: 0, Element: 1
Index: 1, Element: 2
Index: 2, Element: 3
Index: 3, Element: 4
Enter fullscreen mode Exit fullscreen mode

As we can see, our example works the same as before.

But the range keyword is quite versatile and can be used in multiple ways.

for i, e := range arr {} // Normal usage of range

for _, e := range arr {} // Omit index with _ and use element

for i := range arr {} // Use index only

for range arr {} // Simply loop over the array
Enter fullscreen mode Exit fullscreen mode

Multi dimensional

All the arrays that we created so far are one dimensional. We can also create multi-dimensional arrays in Go.

Let's take a look at an example:

func main() {
    arr := [2][4]int{
        {1, 2, 3, 4},
        {5, 6, 7, 8},
    }

    for i, e := range arr {
        fmt.Printf("Index: %d, Element: %d\n", i, e)
    }
}
Enter fullscreen mode Exit fullscreen mode
$ go run main.go
Index: 0, Element: [1 2 3 4]
Index: 1, Element: [5 6 7 8]
Enter fullscreen mode Exit fullscreen mode

We can also let the compiler infer the length of the array by using ... ellipses instead of the length.

func main() {
    arr := [...][4]int{
        {1, 2, 3, 4},
        {5, 6, 7, 8},
    }

    for i, e := range arr {
        fmt.Printf("Index: %d, Element: %d\n", i, e)
    }
}
Enter fullscreen mode Exit fullscreen mode
$ go run main.go
Index: 0, Element: [1 2 3 4]
Index: 1, Element: [5 6 7 8]
Enter fullscreen mode Exit fullscreen mode

Properties

Now let's talk about some properties of arrays.

The array's length is part of its type. So, the array a and b are completely distinct types, and we cannot assign one to the other.

This also means that we cannot resize an array, because resizing an array would mean changing its type.

package main

func main() {
    var a = [4]int{1, 2, 3, 4}
    var b [2]int = a // Error, cannot use a (type [4]int) as type [2]int in assignment
}
Enter fullscreen mode Exit fullscreen mode

Arrays in Go are value types unlike other languages like C, C++, and Java where arrays are reference types.

This means that when we assign an array to a new variable or pass an array to a function, the entire array is copied.

So, if we make any changes to this copied array, the original array won't be affected and will remain unchanged.

package main

import "fmt"

func main() {
    var a = [7]string{"Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"}
    var b = a // Copy of a is assigned to b

    b[0] = "Monday"

    fmt.Println(a) // Output: [Mon Tue Wed Thu Fri Sat Sun]
    fmt.Println(b) // Output: [Monday Tue Wed Thu Fri Sat Sun]
}
Enter fullscreen mode Exit fullscreen mode

Slices

I know what you're thinking, arrays are useful but a bit inflexible due to the limitation caused by their fixed size.

This brings us to Slice, so what is a slice?

A Slice is a segment of an array. Slices build on arrays and provide more power, flexibility, and convenience.

slice

A slice consists of three things:

  • A pointer reference to an underlying array.
  • The length of the segment of the array that the slice contains.
  • And, the capacity, which is the maximum size up to which the segment can grow.

Just like len function, we can determine the capacity of a slice using the built-in cap function. Here's an example:

package main

import "fmt"

func main() {
    a := [5]int{20, 15, 5, 30, 25}

    s := a[1:4]

    // Output: Array: [20 15 5 30 25], Length: 5, Capacity: 5
    fmt.Printf("Array: %v, Length: %d, Capacity: %d\n", a, len(a), cap(a))

    // Output: Slice [15 5 30], Length: 3, Capacity: 4
    fmt.Printf("Slice: %v, Length: %d, Capacity: %d", s, len(s), cap(s))
}
Enter fullscreen mode Exit fullscreen mode

Don't worry, we are going to discuss everything shown here in detail.

Declaration

Let's see how we can declare a slice.

var s []T
Enter fullscreen mode Exit fullscreen mode

As we can see, we don't need to specify any length. Let's declare a slice of integers and see how it works.

func main() {
    var s []string

    fmt.Println(s)
    fmt.Println(s == nil)
}
Enter fullscreen mode Exit fullscreen mode
$ go run main.go
[]
true
Enter fullscreen mode Exit fullscreen mode

So, unlike arrays, the zero value of a slice is nil.

Initialization

There are multiple ways to initialize our slice. One way is to use the built-in make function.

make([]T, len, cap) []T
Enter fullscreen mode Exit fullscreen mode
func main() {
var s = make([]string, 0, 0)

    fmt.Println(s)
}
Enter fullscreen mode Exit fullscreen mode
$ go run main.go
[]
Enter fullscreen mode Exit fullscreen mode

Similar to arrays, we can use the slice literal to initialize our slice.

func main() {
    var s = []string{"Go", "TypeScript"}

    fmt.Println(s)
}
Enter fullscreen mode Exit fullscreen mode
$ go run main.go
[Go TypeScript]
Enter fullscreen mode Exit fullscreen mode

Another way is to create a slice from an array. Since a slice is a segment of an array, we can create a slice from index low to high as follows.

a[low:high]
Enter fullscreen mode Exit fullscreen mode
func main() {
    var a = [4]string{
        "C++",
        "Go",
        "Java",
        "TypeScript",
    }

    s1 := a[0:2] // Select from 0 to 2
    s2 := a[:3]  // Select first 3
    s3 := a[2:]  // Select last 2

    fmt.Println("Array:", a)
    fmt.Println("Slice 1:", s1)
    fmt.Println("Slice 2:", s2)
    fmt.Println("Slice 3:", s3)
}
Enter fullscreen mode Exit fullscreen mode
$ go run main.go
Array: [C++ Go Java TypeScript]
Slice 1: [C++ Go]
Slice 2: [C++ Go Java]
Slice 3: [Java TypeScript]
Enter fullscreen mode Exit fullscreen mode

Missing low index implies 0 and missing high index implies len(a).

The thing to note here is we can create a slice from other slices too and not just arrays.

    var a = []string{
        "C++",
        "Go",
        "Java",
        "TypeScript",
    }
Enter fullscreen mode Exit fullscreen mode

Iteration

We can iterate over a slice in the same way you iterate over an array, by using the for loop with either len function or range keyword.

Functions

So now, let's talk about built-in slice functions provided in Go.

  • copy

The copy() function copies elements from one slice to another. It takes 2 slices, a destination, and a source. It also returns the number of elements copied.

func copy(dst, src []T) int
Enter fullscreen mode Exit fullscreen mode

Let's see how we can use it.

func main() {
    s1 := []string{"a", "b", "c", "d"}
    s2 := make([]string, len(s1))

    e := copy(s2, s1)

    fmt.Println("Src:", s1)
    fmt.Println("Dst:", s2)
    fmt.Println("Elements:", e)
}
Enter fullscreen mode Exit fullscreen mode
$ go run main.go
Src: [a b c d]
Dst: [a b c d]
Elements: 4
Enter fullscreen mode Exit fullscreen mode

As expected, our 4 elements from the source slice were copied to the destination slice.

  • append

Now, let's look at how we can append data to our slice using the built-in append function which appends new elements at the end of a given slice.

It takes a slice and a variable number of arguments. It then returns a new slice containing all the elements.

append(slice []T, elems ...T) []T
Enter fullscreen mode Exit fullscreen mode

Let's try it in an example by appending elements to our slice.

func main() {
    s1 := []string{"a", "b", "c", "d"}

    s2 := append(s1, "e", "f")

    fmt.Println("s1:", s1)
    fmt.Println("s2:", s2)
}
Enter fullscreen mode Exit fullscreen mode
$ go run main.go
s1: [a b c d]
s2: [a b c d e f]
Enter fullscreen mode Exit fullscreen mode

As we can see, the new elements were appended and a new slice was returned.

But if the given slice doesn't have sufficient capacity for the new elements then a new underlying array is allocated with a bigger capacity.

All the elements from the underlying array of the existing slice are copied to this new array, and then the new elements are appended.

Properties

Finally, let's discuss some properties of slices.

Slices are reference types, unlike arrays.

This means modifying the elements of a slice will modify the corresponding elements in the referenced array.

package main

import "fmt"

func main() {
    a := [7]string{"Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"}

    s := a[0:2]

    s[0] = "Sun"

    fmt.Println(a) // Output: [Sun Tue Wed Thu Fri Sat Sun]
    fmt.Println(s) // Output: [Sun Tue]
}
Enter fullscreen mode Exit fullscreen mode

Slices can be used with variadic types as well.

package main

import "fmt"

func main() {
    values := []int{1, 2, 3}
    sum := add(values...)
    fmt.Println(sum)
}

func add(values ...int) int {
    sum := 0
    for _, v := range values {
        sum += v
    }

    return sum
}
Enter fullscreen mode Exit fullscreen mode

This article is part of my open source Go Course available on Github.

GitHub logo karanpratapsingh / learn-go

Master the fundamentals and advanced features of the Go programming language

Learn Go

Hey, welcome to the course, and thanks for learning Go. I hope this course provides a great learning experience.

This course is also available on my website and as an ebook on leanpub. Please leave a ⭐ as motivation if this was helpful!

Table of contents

What is Go?

Go (also known as Golang) is a programming language developed at Google in 2007 and open-sourced in 2009.

It focuses on simplicity, reliability, and efficiency. It was designed to combine the efficacy, speed…

Top comments (0)