Golang Slices and Arrays


Golang is an open source programming language used largely for server-side programming and is developed by Google.

With it static typing, it is a very simple and versatile programming language that is an excellent choice for beginners. Golang is a type-safe language and has a flexible and powerful type system.

In addition, its syntax is very simple, making your code easier to read. It is a derivative of C and some features taken from other languages such as garbage collection (from Java) and few simple dynamic typing.

Arrays and slices in data structure are the key ingredients to any Go program. These are the main building blocks from which complex software is built. Having a good understanding of these foundation concepts is crucial when designing software.

Even though arrays seem simple, when it comes to usage you will have many questions.

  • Is it fixed or variable in size?
  • Does the size determine the type?
  • Do multidimensional arrays have any specific characteristics?
  • And what about empty arrays?

In order to respond to the above questions, the Go development team introduced Slices, which are dynamic-sized arrays and give a flexible, extensible data structure.

We will cover the following sections in this article.

  1. Arrays in Golang

    i) Array Declaration

    ii) Accessing Go array

    iii) Multidimensional array

    iv) Passing arrays between functions

    v) Points to be remembered while working with Go array

  2. Slices in Golang

    i) Creating a Slice

    ii) Components in a Slice

    iii) Creating a Slice using built-in make() function

    iv) Slice of Slices

    v) Iterate over Slice

    vi) Functions in Slices

    vii) Passing slice between functions

#1 Arrays in Golang

Arrays in Golang are simply sequences or homogeneous collections of elements with similar data types in the memory.

The values of the array are called elements or items. Arrays can contain zero or more than zero values.

An array element can be accessed by an index.

Consider a scenario where we have numbers up to 100 and we want to store them in a variable.

In contrast to storing them individually as num0, num1 till num99, storing them in an array called num and accessing each number as num[0], num[1], etc. is much more straightforward.

Go Array

i) Array Declaration

In Go, arrays can be declared using two methods.

  1. Using var keyword
  2. Using := sign (Shorthand Declaration Method)

a) Using var keyword:

var array_name = [length]datatype{values} // here length is defined

or

var array_name = [...]datatype{values} // here length is inferred
Array Declaration

b) Using := sign:

array_name := [length]datatype{values} // here length is defined

or

array_name := [...]datatype{values} // here length is inferred
Array Declaration using := sign

Golang Array Examples:

package main
import (
    "fmt"
)

func main() {
	// an array with datatype string declared using keyword var
	var monitoring_tools = [3]string{"apm", "rum", "infra"}

	//an array with datatype integers declared using := sign
	num := [5]int{1,5,44,89,12}

	fmt.Println(monitoring_tools)
	fmt.Println(num)
}
Array example with defined length

Output:

Output of Array example with defined length
package main
import (
    "fmt"
)

func main() {
  var odd_num = [...]int{1,3,9}
  even_num := [...]int{4,8,16,72,80}

  fmt.Println(odd_num)
  fmt.Println(even_num)
}
Array example with inferred length
Output of Array example with inferred length

ii) Accessing Go array

Accessing array elements is very much easier since the memory is laid out in sequence. To access the elements we can use [] operator.

// Declare an integer array of five elements.
// Initialize each element with a specific value.
array := [5]int{10, 20, 30, 40, 50}

// Change the value at index 2.
array[4] = 55
Accessing array elements

iii) Multidimensional Array

Multidimensional array ia an array with more than 2 dimensions which are represented by rows and columns.

General form of a multidimensional array declaration:

var variable_name [SIZE1][SIZE2]...[SIZEN] variable_type


//example
var threedim [5][10][4]int

Example of 2-D array:

// Declare a two dimensional integer array of four elements
// by two elements.
var array [4][2]int

// Use an array literal to declare and initialize a two
// dimensional integer array.
array := [4][2]int{{10, 11}, {20, 21}, {30, 31}, {40, 41}}

// Declare and initialize index 1 and 3 of the outer array.
array := [4][2]int{1: {20, 21}, 3: {40, 41}}

// Declare and initialize individual elements of the outer
// and inner array.
array := [4][2]int{1: {0: 20}, 3: {1: 41}}
Accessing elements of a two-dimensional array:
// Declare a two dimensional integer array of two elements.
var array [2][2]int

// Set integer values to each individual element.
array[0][0] = 10
array[0][1] = 20
array[1][0] = 30
array[1][1] = 40

You can copy the multidimensional array to another array when they have the same datatype.

Assigning multidimensional arrays of the same type:
// Declare two different two dimensional integer arrays.
var array1 [2][2]int
var array2 [2][2]int

// Add integer values to each individual element.
array2[0][0] = 10
array2[0][1] = 20
array2[1][0] = 30
array2[1][1] = 40

// Copy the values from array2 into array1.
array1 = array2

Since array is an value you can also copy the individual dimensions.

// Copy index 1 of array1 into a new array of the same type.
var array3 [2]int = array1[1]

// Copy the integer found in index 1 of the outer array
// and index 0 of the interior array into a new variable of
// type integer.
var value int = array1[1][0]

Example of multidimensional array:

package main

import ( "fmt")

func printarray(a [3][2]string) {  
    for _, v1 := range a {
        for _, v2 := range v1 {
            fmt.Printf("%s ", v2)
        }
        fmt.Printf("\n")
    }
}

func main() {  
    companies := [3][2]string{
        {"apple", "microsoft"},
        {"meta", "netflix"},
        {"atatus", "amazon"},
    }

    printarray(companies)
 
    var monitoring_tools [3][2]string
    
    monitoring_tools[0][0] = "apm"
    monitoring_tools[0][1] = "rum"
    monitoring_tools[1][0] = "synthetics"
    monitoring_tools[1][1] = "logs"
    monitoring_tools[2][0] = "api analytics"
    monitoring_tools[2][1] = "error monitoring"
    
    fmt.Printf("\n")
    printarray(monitoring_tools)
}

Output:

Go Multidimensional Array

iv) Passing arrays between functions

An array passed between functions might take too much memory and performance because variables are always passed by value when they are passed between functions, whereas when the variable is passed as an array, no matter what its size is, it is copied.

// Declare an array
var array []int

// Pass the array to the function foo.
foo(array)

func foo(array []int) {
    ...
    return array
}

v) Points to be remembered while working with Go array

The arrays in Golang are mutable, which means that the values can easily be altered with the syntax detailed below.

var array_name[index] = element

Example:

package main
import ("fmt")

func main() {
	
	var monitoring_tools = [3]string{"apm", "rum", "infra"}
	var monitoring_tools[1] = "synthetic"
	fmt.Println(monitoring_tools)
}
  • Array elements can be accessed using index value or by for loop.

Example:

package main
import ("fmt")

func main() {
	
	var monitoring_tools = [3]string{"apm", "rum", "infra"}
    
	// Using index value
	fmt.Println(monitoring_tools[1])
    
	// Using for loop
	for i:= 0; i < len(monitoring_tools); i++{
		fmt.Println(monitoring_tools[i])
	}
}
  • Duplicate elements can be stored in an array.
  • Arrays in Go are generally one-dimensional.
  • In Golang, array is of value type not reference type.

Example:

package main
  
import ("fmt")
  
func main() {
      
	// Creating an array with inferred array length
	tools:= [...]string{"apm", "rum", "synthetic", "infra", "logs"}
	fmt.Println("Original tools array(Before):", tools)
  
	// Creating a new variable and initialize with tools
	monitoring_tools := tools

	fmt.Println("New monitoring tools array(before):", monitoring_tools)
  
	// Change the value at index 0 to application performance monitoring
	monitoring_tools[0] = "application performance monitoring"
  
	fmt.Println("New monitoring tools array(After):", monitoring_tools)
  
	fmt.Println("Original tools array(After):", tools)
}
  • The array's type is also comparable when its elements are compared using the == operator.

Go programs tend not to use arrays due to their fixed size that limits their expressive power. Fortunately, slices can help in this regard. Scroll down to know more about Slices.

#2 Slices in Golang

Slices are a lightweight and variable-length sequence Go data structure that is more powerful, flexible and convenient than arrays.

The slices also support storing multiple elements of the same type in a single variable, just as arrays do. Slices, on the other hand, permit you to change the length whenever you like.

The problem with raw arrays is that the size can't be changed once created, the element sizes are fixed.

For example, imagine you have an array that contains addresses for each person in your company. You can't change the addresses in the array after you have added a new person to the company, instead you need to add a new person to the array.

To use Slices effectively, you need to understand what they are and what they do.

i) Creating a Slice

Slices are declared like arrays, but without specifying what size they are. The size can therefore be changed as necessary.

The type T of a slice is represented as []T.

You can create slice using three methods.

  • Creating slice using literals.
  • Creating a slice from an array.
  • Creating a slice from another slice.

a) Creating a slice using literals

As with arrays, slice literals can be declared using the var keyword without mentioning the size in the square brackets.

// Creating a slice using a slice literal
var num = []int{1, 2, 3, 4, 5}

Complete example of creating slice with literals.

package main
import ("fmt")

func main() {
	// Creating a slice using a slice literal
	var odd_num = []int{3, 5, 7, 9, 11, 13, 17}

	// Usng := sign
	even_num := []int{2, 4, 8, 16, 32, 64}

	fmt.Println("Odd numbers are = ", odd_num)
	fmt.Println("Even numbers are = ", even_num)
}

Output:

Go Slice Example

b) Creating a slice using arrays

Slices are segments of an array, so they can be declared by using a lower bound(low) and an upper bound(up). This is done by separating the two values with a colon.

arr[low:up]

Example:

package main
import ("fmt")

func main() {
	// Initializing an array
	var odd_num = [7]int{3, 5, 7, 9, 11, 13, 17}

	// Creating a slice from the array
	var odd_slice []int = odd_num[1:4]

	fmt.Println("Array of odd numbers = ", odd_num)
	fmt.Println("Slice = ", odd_slice)
}

Output:

Slice using Arrays:

The default value of indices low and high are 0 and length of the slice respectively.

Below is an example of a slice with strings.

package main
import ("fmt")

func main() {
	monitoring := [5]string{"APM", "RUM", "Synthetics", "Infra", "Logs"}

	slice1 := monitoring[1:4]
	slice2 := monitoring[:3]
	slice3 := monitoring[2:]
	slice4 := monitoring[:]

	fmt.Println("Array = ", monitoring)
	fmt.Println("slice1 = ", slice1)
	fmt.Println("slice2 = ", slice2)
	fmt.Println("slice3 = ", slice3)
	fmt.Println("slice4 = ", slice4)
}

c) Creating a slice from another slice

The existing slice can be sliced to create a new slice.

Example:

package main
import ("fmt")

func main() {
	cities := []string{"New York", "London", "Chicago", "Beijing", "Delhi", "Mumbai", "Bangalore", "Hyderabad", "Hong Kong"}

	asianCities := cities[3:]
	indianCities := asianCities[1:5]

	fmt.Println("cities = ", cities)
	fmt.Println("asianCities = ", asianCities)
	fmt.Println("indianCities = ", indianCities)
}

Output:

Creating slice from another slice

ii) Components in a Slice

Slice consists of three elements:

  1. Pointers - They are special variables in slice that are used to store the memory address of another variable. This is used to point a particular elements in an array.
  2. Length - It is the total number of elements present in an array.
  3. Capacity - It determines the amount of size upto which an array can expand or shrink.
Slice Components

Examples of components:

var a = [6]int{100, 200, 300, 400, 500, 600}
var s = [1:4]
Example of slice components

The variable s is created from the indices 1 to 4. Therefore the length of a is 6 and b is 5 and the capacity of a is 6 and b is 5 since the variable s has been created from the variable a of index 1.

Example using string:

// Golang program to illustrate
// the working of the slice components

package main
 
import ("fmt")
 
func main() {
 
    // Creating an array
    arr := [7]string{"Atatus", "offers", "full-stack", "monitoring",
                         "tools"}
 
    // Display array
    fmt.Println("Array:", arr)
 
    // Creating a slice
    myslice := arr[0:6]
 
    // Display slice
    fmt.Println("Slice:", myslice)
 
    // Display length of the slice
    fmt.Printf("Length of the slice: %d", len(myslice))
 
    // Display the capacity of the slice
    fmt.Printf("\nCapacity of the slice: %d", cap(myslice))
}

The length of the slice can be extended up to its capacity when re-slicing. A slice that exceeds its length will trigger an runtime error.

Below given is an example to understand the re-slicing concept.

package main
import ("fmt")

func main() {
	s := []int{2, 7, 3, 9, 10, 56, 78, 23, 89, 100}
	fmt.Println("Original Slice")
	fmt.Printf("s = %v, len = %d, cap = %d\n", s, len(s), cap(s))

	s = s[1:5]
	fmt.Println("\nSlicing index 2 through 8")
	fmt.Printf("s = %v, len = %d, cap = %d\n", s, len(s), cap(s))

	s = s[:8]
	fmt.Println("\nAfter extending the length")
	fmt.Printf("s = %v, len = %d, cap = %d\n", s, len(s), cap(s))

	s = s[2:]
	fmt.Println("\nAfter removing the first two elements")
	fmt.Printf("s = %v, len = %d, cap = %d\n", s, len(s), cap(s))
}

iii) Creating a Slice using built-in make() function

Golang also offers a built-in function called make() that can be used to create slices.

func make([]T, len, cap) []T

The make function takes three parameters: a type, a length, and an optional capacity. Slices refer to an array with an underlying capacity equal to the given size.

Example:

slice := make([]string, 2, 3)

An example of creating a slice with make() function.

package main

import ("fmt")

func main() {  
    i := make([]int, 5, 5)
    fmt.Println(i)
}

The values of the slice created using the make() function will be [0 0 0 0 0] respectively.

iv) Slice of Slices

These are just nested slices. Various types of slices can be created, which can contain slices inside slices using one single slice.

package main

import ("fmt")

func main() {
	monitoring_tools := [][]string{
		{"rum", "apm"},
		{"synthetic", "logs"},
		{"analytics", "infra"},
	}

	fmt.Println("Slice monitoring_tools = ", monitoring_tools)
	fmt.Println("length = ", len(monitoring_tools))
	fmt.Println("capacity = ", cap(monitoring_tools))
}

v) Iterate over Slice

The process of developing an application or undertaking any task generally involves iteration to accomplish something in collection.

Golang slices can be iterated in either of the following two ways:

a.) Using a for...range loop

package main

import ("fmt")

func main() {
	numbers := []int{1, 13, 120, 345, 1902}
	for index, value := range numbers {
		fmt.Printf("Index: %d, Value: %d\n", index, value)
	}
}

The index can be initialized with only one variable if the value is not necessary.

for index := range numbers {
	fmt.Printf("Indexes are: %d", index)
}

If you only need the value, you can initialize it as follows:

package main
import ("fmt")

func main() {
	numbers := []float64{2.5, 7.3, 14.8, 54.4}

	sum := 0.0
	for _, number := range numbers {
		sum += number
	}

	fmt.Printf("Total Sum = %.2f\n", sum)
}

b.) Using a for loop

package main
import ("fmt")

func main() {
	countries := []string{"India", "America", "Russia", "England"}

	for i := 0; i < len(countries); i++ {
		fmt.Println(countries[i])
	}
}

vi) Functions in Slice

  1. Copy
  2. Append
  3. Remove

Slice Copy

With the built-in function copy() in Go you can easily copy the source slice into the destination slice. To copy the slice to another slice without affecting the original, the copy function is used.

Example:

func main() {
	slice1 := []int{1, 2, 3, 4, 5}
	slice2 := make([]int, len(s1))

	copy(slice2, slice1)
	fmt.Println(slice1, slice2) // [1 2 3 4 5] [1 2 3 4 5]

	slice2[1] = 10 // changing s2 does not affect s1
	fmt.Println(slice1, slice2) // [1 2 3 4 5] [1 10 3 4 5]
}

When using the copy function, the first argument is the destination slice, and the second argument is the source slice. Both slices should have the same datatype.

During the copying of slices, the length of both slices may or may not be equal. Copying to an Nil slice will result in nothing being copied since it will only copy the smallest number of elements.

Example:

func main() {
	s1 := []int{1, 2, 3, 4, 5} // s1 is length 5
	s2 := make([]int, 3)       // s2 is length 3

	copy(s2, s1)
	fmt.Println(s2) // [1 2 3]

	var s3 []int // nil slice of length 0
	copy(s3, s1)
	fmt.Println(s3) // []
}

Slice Append

As we all know, it is not possible to change or increase the length of an array, but in slices, it is dynamic and we can add additional elements in existing slices.p

To add or append a slice at the end of another slice you can use append() function in Go.

slice = append(slice, elem1, elem2, ...)

Slices can be appended directly to each other by using the ... operator. In append() function, slice is the first parameter and the next parameter can be either one or more values to be appended.

package main
import ("fmt")

func main() {
	slice1 := []string{"Kiwi", "Orange", "Mango"}
	slice2 := []string{"Berry", "Cherry", "Grapes"}

	slice3 := append(slice1, slice2...)

	fmt.Println("slice1 = ", slice1)
	fmt.Println("slice2 = ", slice2)
	fmt.Println("After appending slice1 & slice2 = ", slice3)
}

Slice Remove

Go doesn't provide a built-in function for removing an element in a slice. There are few strategies to remove the element.

Example #1 - If the slice order is important

When we want to preserve the order of slices after removing an element, we move the elements to the left of the deleted element by one.

package main

import ("fmt")

func remove(slice []int, s int) []int {
    return append(slice[:s], slice[s+1:]...)
}

func main() {
    var Slice1 = []int{1, 2, 3, 4, 5}
    fmt.Printf("slice1: %v\n", Slice1)

    Slice2 := remove(Slice1, 2)
    fmt.Printf("slice2: %v\n", Slice2)
}

Output:

Example #2 - If the slice order is not important

package main

import ("fmt")

func remove(s []int, i int) []int {
    s[i] = s[len(s)-1]
    return s[:len(s)-1]
}

func main() {
    var Slice1 = []int{1, 2, 3, 4, 5}
    fmt.Printf("slice1: %v\n", Slice1)

    Slice2 := remove(Slice1, 2)
    fmt.Printf("slice2: %v\n", Slice2)
}

Output:

Removing elements if your slice order is not important

Example #3 - Remove elements from a slice while iterating

Multiple elements can be removed from a slice by iterating over it. You must, however, iterate from the end to the beginning, rather than the other way around.

It is because the slice length keeps decreasing when an element is removed that you will experience the index out of bounds error if you start iterating from the start.

package main

import ("fmt")

func main() {
	num := []int{1, 2, 3, 4, 5, 6}
	for i := len(num) - 1; i >= 0; i-- {
		// this checks whether the integer is even
		// before removing it from the slice
		if num[i]%2 == 0 {
			num = append(num[:i], num[i+1:]...)
		}

	}

	fmt.Println(num) // [1,3,5]
}

Output:

Remove elements from a slice while iterating

vii) Passing Slice between functions

The pointer variable within a slice's argument will refer to the same underlying array, even if the slice is passed by value. So, when a slice is passed to a function, changes made inside the function are visible outside it as well.

package main
  
import ("fmt")
  
// A function called change in which slice
// is passed by value
func change(element []string) {
  
    // Modifying the given slice
    element[2] = "api analytics"
    fmt.Println("Modified slice: ", element)
}
  
// Main function
func main() {
  
    // Creating slice
    monitoring_tools := []string{"apm", "rum", "logs", "synthetics"}
      
    fmt.Println("Initial slice: ", monitoring_tools)
  
    // Passing the slice to the function
    change(monitoring_tools)
      
    fmt.Println("Final slice:", monitoring_tools)
  
}

Output:

Passing Go Slice between function

No matter how many functions they pass in, the slice pointer always points to the same reference.

For example, when we change the value logs to api analytics at index value 2, the slice pointer points to reference 2. After this change, the slice outside the function is also reflected, so the final slice is [apm rum api analytics synthetics].

Conclusion

In summary, slices are an easy way for programmers to create dynamic structures. They also allow programmers to create very efficient structures.

Following are a few considerations when using arrays and slices.

  1. Arrays are used when the entity is represented by a collection of non-empty items.
  2. To describe a general collection that you can add to or remove elements from, you should consider slices.
  3. You can define slices to describe collections containing an unlimited number of elements.
  4. Does the collection need to be modified in any way? If so, use slices.

You now have a solid understanding of how slices and arrays work in Go.

Slices become much easy to use once you gain an understanding of how they work, especially with the built-in functions for copy and append.

Go slice was created as an alternative to arrays, but it can be used as an alternative to pointers, and as a simple way to make it easier for novice programmers to manage memory.