You’ve probably heard of data structures and have used them in other programming languages, but do you know how to use them in Go?
As one of the fastest-growing programming languages in the industry, it is important for devs to understand how to utilize this vital feature in order to create scalable, reliable applications.
In this article, we will be covering data structures in Go, and taking a deep dive into concepts like arrays, slices, maps, and structs. Plus, I’ll provide multiple code examples along the way.
To follow and understand this tutorial, you will need the following:
An array is a collection of data of a specific type. It stores multiple values in a single variable where each element has an index to reference itself.
Arrays come in handy when you need to keep more than one thing in a single location, like a list of people who attended an event or the age of students in a class.
To create an array, we need to define its name, length, and type of values we will be storing:
var studentsAge [10]int
In this code blog, we created an array named studentsAge
, which can store a maximum of ten int
values.
You can create an array from literals, meaning you’re assigning values to them at the point of creation.
Let’s see how it can be used:
// creating an array and assigning values later var studentsAge [10]int studentsAge = [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10} // creating and assigning values to an array var studentsAge = [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10} // creating and assigning values to an array without var keyword studentsAge := [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
You can create an array where every element is an individual array (nested arrays), like so:
// creating a nested array nestedArray := \[3\][5]int{ {1, 2, 3, 4, 5}, {6, 7, 8, 9, 10}, {11, 12, 13, 14, 15}, } fmt.Println(nestedArray) // \[[1 2 3 4 5\] [6 7 8 9 10] [11 12 13 14 15]]
Each element in an array has an index that you can use to access and modify its value. The index of an array is always an integer and starts counting from zero:
// creating an array of integers studentsAge := [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10} // accessing array values with their indexes fmt.Println(studentsAge[0]) // 1 fmt.Println(studentsAge[1]) // 2 fmt.Println(studentsAge[9]) // 10 // using a for loop to access an array for i := 0; i < 10; i++ { fmt.Println(studentsAge[i]) } // using range to access an array for index, value := range studentsAge { fmt.Println(index, value) }
Arrays are a mutable data structures, so it is possible to modify their values after creation:
// creating an array of integers studentsAge := [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10} // modifying array values with their indexes studentsAge[0] = 5 studentsAge[4] = 15 studentsAge[7] = 10 fmt.Println(studentsAge) // [5 2 3 4 15 6 7 10 9 10]
Go provides a len
function that you can use to get the length of an array.
Let’s see how it can be used:
// creating and getting the length of an array with a length of 10 var arrayOfIntegers [10]int fmt.Println(len(arrayOfIntegers)) // 10 // creating and getting the length of an array with a length of 7 var arrayOfStrings [7]string fmt.Println(len(arrayOfStrings)) // 7 // creating and getting the length of an array with a length of 20 var arrayOfBooleans [20]bool fmt.Println(len(arrayOfBooleans)) // 20
Note that it is impossible to change the length of an array because it becomes part of the type during creation.
Like arrays, slices allow you to store multiple values of the same type in a single variable and access them with indexes. The main difference between slices and arrays is that slices have dynamic lengths, while arrays are fixed.
To create a slice, we need to define its name and the type of values we will be storing:
var sliceOfIntegers []int
We created a slice named sliceOfIntegers
, which stores int
values.
In its original form, a slice is an extracted portion of an array. To create a slice from an array, we need to provide Go with the part to extract.
Let’s see how to do so:
// creating an array of integers studentsAge := [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10} // creating slices from arrays fiveStudents := studentsAge[0:5] fmt.Println(fiveStudents) // [1 2 3 4 5] threeStudents := studentsAge[3:6] fmt.Println(threeStudents) // [4 5 6]
The slicing format requires you to provide the indexes to start and stop the Go slice extraction. If any of the parameters are omitted, Go uses zero as the starting point (beginning of the array) and the array’s length if the ending is omitted:
// creating an array of integers studentsAge := [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10} // creating slices from arrays fmt.Println(studentsAge[:4]) // [1 2 3 4] fmt.Println(studentsAge[6:]) // [7 8 9 10] fmt.Println(studentsAge[:]) // [1 2 3 4 5 6 7 8 9 10]
It is also possible to create slices from other slices with the same format as arrays:
// creating an array of integers studentsAge := [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10} // creating slices from arrays firstSlice := studentsAge[:8] fmt.Println(firstSlice) // [1 2 3 4 5 6 7 8] // creating slices from slices secondSlice := firstSlice[1:5] fmt.Println(secondSlice) // [2 3 4 5]
make
Go provides a make
function that you can use to create slices by specifying their length. After creation, Go will fill the slice with the zero value of its type:
// creating slices with make specifying length sliceOfIntegers := make([]int, 5) // [0 0 0 0 0] sliceOfBooleans := make([]bool, 3) // [false false false]
Every slice has a length and a capacity. The length of the slice is the number of elements in the slice, while the capacity is the number of elements in the underlying array, counted from the first element in the slice.
The make
function allows us to create a slice with a specified capacity. Here’s the usage:
// creating a slice with a length of 5 and a capacity of 10 sliceOfStrings := make([]string, 5, 10)
You can create a slice from literals, meaning you’re assigning values to them at the point of creation:
// creating a slice and assigning values later var tasksRemaining []string tasksRemaining = []string{"task 1", "task 2", "task 3"} // creating and assigning values to a slice var tasksRemaining = []string{"task 1", "task 2", "task 3"} // creating and assigning values to a slice without var keyword tasksRemaining := []string{"task 1", "task 2", "task 3"}
You can create a slice in which every element is an individual slice (nested slices), like so:
// creating a nested slice nestedSlice := [][]int{ {1}, {2, 3}, {4, 5, 6}, {7, 8, 9, 10}, } fmt.Println(nestedSlice) // \[[1\] [2 3] \[4 5 6\] [7 8 9 10]]
Each element in a slice has an index that you can use to access and modify its value. The index of a slice is always an integer and starts counting from zero:
// creating a slice from literals sliceOfIntegers := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10} // accessing slice values with their indexes firstInteger := sliceOfIntegers[0] // 1 secondInteger := sliceOfIntegers[1] // 2 lastInteger := sliceOfIntegers[9] // 10 // using a for loop to access a slice for i := 0; i < 10; i++ { fmt.Println(sliceOfIntegers[i]) } // using range to access a slice for index, value := range sliceOfIntegers { fmt.Println(index, value) }
Slices are mutable data structures, so it is possible to modify their values after creation:
// creating a slice from literals sliceOfIntegers := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10} sliceOfIntegers[0] = 3 sliceOfIntegers[5] = 2 sliceOfIntegers[9] = -10 fmt.Println(sliceOfIntegers) // [3 2 3 4 5 2 7 8 9 -10]
Go provides a len
function that you can use to get the length of a slice:
// creating and getting the length of a slice sliceOfIntegers := make([]int, 10) fmt.Println(len(sliceOfIntegers)) // 10
There is also the cap
function, which you can use to get the capacity of a slice:
// creating and getting the capacity of a slice sliceOfIntegers := make([]int, 10, 15) fmt.Println(cap(sliceOfIntegers)) // 15
Go provides an append
function that you can use to add elements to an existing slice:
// creating a slice from literals sliceOfIntegers := []int{1, 2, 3} // using append to add a single value to the slice sliceOfIntegers = append(sliceOfIntegers, 4) fmt.Println(sliceOfIntegers) // [1 2 3 4] // using append to add multiple values to the slice sliceOfIntegers = append(sliceOfIntegers, 5, 6, 7) fmt.Println(sliceOfIntegers) // [1 2 3 4 5 6 7] // using append to add a slice to a slice anotherSlice := []int{8, 9, 10} sliceOfIntegers = append(sliceOfIntegers, anotherSlice...) fmt.Println(sliceOfIntegers) // [1 2 3 4 5 6 7 8 9 10]
The append
function is variadic and accepts a variable number of arguments. This is why we can pass multiple values to it by separating them with commas.
A map is a data structure that assigns keys to its values (key-value pairs). It is similar to Objects in JavaScript, HashMap in Java, and Dictionaries in Python. The zero value of a map is nil
.
To create a map, we need to define its name and the data type for its keys and values:
var studentsAge map[string]int
Here, we created a map named studentsAges
, which stores its keys as strings
and values as ints
.
make
Go provides a make
function that you can use to initialize maps you have created:
// creating a string -> int map var studentsAge map[string]int studentsAge = make(map[string]int)
Maps must be initialized with make
after their creation before assigning values to them.
You can also create maps with make
. Doing so doesn’t require you to initialize it again before using:
// creating a string -> int map studentsAge := make(map[string]int)
Creating a map from literals means assigning their keys and values at the point of creation. Let’s see how it can be used:
// creating a map from literals studentsAge := map[string]int{ "solomon": 19, "john": 20, "janet": 15, "daniel": 16, "mary": 18, } fmt.Println(studentsAge) // map[daniel:16 janet:15 john:20 mary:18 solomon:19]
You can create a map where every key references another map (nested maps), like so:
// creating nested maps studentResults := map[string]map[string]int{ "solomon": {"maths": 80, "english": 70}, "mary": {"maths": 74, "english": 90}, } fmt.Println(studentResults) // map[mary:map[english:90 maths:74] solomon:map[english:70 maths:80]] fmt.Println(studentResults["solomon"]) // map[english:70 maths:80] fmt.Println(studentResults\["solomon"\]["maths"]) // 80
In this code block, we created a map with string
keys, and each value is another map with string
keys and int
values.
To add values to a map, you need to assign the key to whichever value you want it to be:
// creating a string -> int map studentsAge := make(map[string]int) // adding values to the map studentsAge["solomon"] = 19 studentsAge["john"] = 20 studentsAge["janet"] = 15 fmt.Println(studentsAge) // map[janet:15 john:20 solomon:19]
To access values in a map, you need to reference the assigned key:
// creating a map from literals studentsAge := map[string]int{ "solomon": 19, "john": 20, "janet": 15, "daniel": 16, "mary": 18, } // accessing values in the map fmt.Println(studentsAge["solomon"]) // 19 fmt.Println(studentsAge["mary"]) // 18 fmt.Println(studentsAge["daniel"]) // 16
There are times when you want to check if a key already exists in a map. Go allows you do this with a two-value assignment to the map value:
// creating a map from literals studentsAge := map[string]int{ "solomon": 19, "john": 20, "janet": 15, "daniel": 16, "mary": 18, } // two-value assignment to get an existing key element, ok := studentsAge["solomon"] fmt.Println(element, ok) // 19 true // two-value assignment to get a non-existing key element, ok = studentsAge["joel"] fmt.Println(element, ok) // 0 false
When a two-value assignment is used to access values in a map, the first value returned is the value of the key in the map, while the second variable is a boolean indicating if the key exists or not.
If the key does not exist, the first value is assigned to the zero value
of the map value type.
To update values in a map, you need to reference an existing key and assign a new value to it:
// creating a map from literals studentsAge := map[string]int{ "solomon": 19, "john": 20, "janet": 15, "daniel": 16, "mary": 18, } // updating values in the map studentsAge["solomon"] = 20 fmt.Println(studentsAge["solomon"]) // 20 // updating values in the map studentsAge["mary"] = 25 fmt.Println(studentsAge["mary"]) // 25
Go provides a delete
function that you can use to remove keys from an existing map:
// creating a map from literals studentsAge := map[string]int{ "solomon": 19, "john": 20, "janet": 15, "daniel": 16, "mary": 18, } fmt.Println(studentsAge) // map[daniel:16 janet:15 john:20 mary:18 solomon:19] // deleting keys from the studentsAge map delete(studentsAge, "solomon") delete(studentsAge, "daniel") fmt.Println(studentsAge) // map[janet:15 john:20 mary:18]
A struct is a collection of data fields with defined data types. Structs are similar to classes in OOP languages, in that they allow developers to create custom data types that hold and pass complex data structures around their systems.
To create a struct, we will use the type
keyword in Go, then define its name and data fields with their respective data types:
type Rectangle struct { length float64 breadth float64 }
We created a struct named Rectangle
with length
and breadth
data fields of type float64
.
Structs are types themselves, so when creating them with the type
keyword, they must be made directly under a package declaration and not inside functions like main
.
To create an instance, we need to define its name, data type for its keys, and data type for its values:
// creating a struct instance with var var myRectangle Rectangle // creating an empty struct instance myRectangle := Rectangle{}
You can create a struct instance from literals, meaning you’re assigning their field values to them at the point of creation:
// creating a struct instance specifying values myRectangle := Rectangle{10, 5} // creating a struct instance specifying fields and values myRectangle := Rectangle{length: 10, breadth: 5} // you can also omit struct fields during their instantiation myRectangle := Rectangle{breadth: 10}
If you omit a struct field during instantiation, it will default to the type’s zero value.
Because structs are data types, it is possible to create arrays and slices of them, like so:
arrayOfRectangles := [5]Rectangle{ {10, 5}, {15, 10}, {20, 15}, {25, 20}, {30, 25}, } fmt.Println(arrayOfRectangles) // [{10 5} {15 10} {20 15} {25 20} {30 25}] sliceOfRectangles := []Rectangle{ {10, 5}, {15, 10}, {20, 15}, {25, 20}, {30, 25}, } fmt.Println(sliceOfRectangles) // [{10 5} {15 10} {20 15} {25 20} {30 25}]
Go also allows creation of struct instances that are pointers to the struct definition:
// creating a pointer struct instance myRectangle := &Rectangle{length: 10, breadth: 5} fmt.Println(myRectangle, *myRectangle) // &{10 5} {10 5}
You can also create a pointer struct instance with new
. Let’s see how:
// creating a struct instance with new myRectangle := new(Rectangle) fmt.Println(myRectangle, *myRectangle) // &{0 0} {0 0}
To access fields in a struct, you need to reference the field name:
// creating a struct instance specifying fields and values myRectangle := Rectangle{length: 10, breadth: 5} // accessing the values in struct fields fmt.Println(myRectangle.length) // 10 fmt.Println(myRectangle.breadth) // 5
To update values in a struct field, you need to reference the field name and assign a new value to it:
// creating a struct instance specifying fields and values myRectangle := Rectangle{length: 10, breadth: 5} fmt.Println(myRectangle) // {10 5} myRectangle.length = 20 myRectangle.breadth = 8 fmt.Println(myRectangle) // {20 8}
Go allows you to use structs as data fields in another struct (nested structs):
// creating a nested struct type address struct { houseNumber int streetName string city string state string country string } type Person struct { firstName string lastName string homeAddress address }
You have to create an instance of the Person
and address
structs when creating a new instance of the Person
struct, like so:
// creating an instance of a nested struct person := Person{ firstName: "Solomon", lastName: "Ghost", homeAddress: address{ houseNumber: 10, streetName: "solomon ghost street", city: "solomon city", state: "solomon state", country: "solomon country", }, } fmt.Println(person.firstName) // Solomon fmt.Println(person.homeAddress.country) // solomon country
Anonymous structs allow you to create structs inside functions and use them on the go. Let’s see how it can be used:
// creating a struct anonymously circle := struct { radius float64 color string }{ radius: 10.6, color: "green", } fmt.Println(circle) // {10.6 green} fmt.Println(circle.color) // green
Struct methods are functions that are attached to a struct. They can only be called via a struct instance and automatically receive the struct instance as parameters.
To create a struct method, we need to define the struct it will be attached to, its name, parameters (if any), and return types (if any). Let’s see it in action:
type Rectangle struct { length float64 breadth float64 } func (r Rectangle) area() float64 { return r.length * r.breadth }
Here, we created an area
method for our Rectangle
struct, which uses field values to calculate and return the shape’s area as float64
. We can proceed to use this in code like so:
// creating a struct instance myRectangle := Rectangle{10, 5} // calling the Rectangle area method fmt.Println(myRectangle.area()) // 50
Structs pass a copy of their instances to methods, so these changes will not reflect if you were to update the value of the fields in the method.
However, there may be cases in which you want to update field values from methods. Go allows methods receive a pointer reference instead of the value itself:
func (r *Rectangle) setLength(length float64) { r.length = length } func (r *Rectangle) setBreadth(breadth float64) { r.breadth = breadth }
We created a setLength
and setBreadth
method for our Rectangle
struct that updates the field variables with arguments we pass to it. We can proceed to use this in code like so:
// creating a struct instance myRectangle := Rectangle{10, 5} fmt.Println(myRectangle) // {10 5} // calling the modifier methods on our instance myRectangle.setLength(20) myRectangle.setBreadth(10) fmt.Println(myRectangle) // {20 10}
In this article, we learned about the various data structures in Go like arrays, slices, maps, and structs. We also showed multiple code examples, use cases, and functions.
I hope this was a useful guide to what can often be a complicated topic. With this article as a reference guide, you can confidently use the correct data structures for your use case and create fast, performant apps.
If you like, head over to the Tour of Go for more references and examples of Go data structures.
Install LogRocket via npm or script tag. LogRocket.init()
must be called client-side, not
server-side
$ npm i --save logrocket // Code: import LogRocket from 'logrocket'; LogRocket.init('app/id');
// Add to your HTML: <script src="https://cdn.lr-ingest.com/LogRocket.min.js"></script> <script>window.LogRocket && window.LogRocket.init('app/id');</script>
Would you be interested in joining LogRocket's developer community?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowBackdrop and background have similar meanings, as they both refer to the area behind something. The main difference is that […]
AI tools like IBM API Connect and Postbot can streamline writing and executing API tests and guard against AI hallucinations or other complications.
Explore DOM manipulation patterns in JavaScript, such as choosing the right querySelector, caching elements, improving event handling, and more.
`window.ai` integrates AI capabilities directly into the browser for more sophisticated client-side functionality without relying heavily on server-side processing.