This content originally appeared on DEV Community and was authored by Andrew Bone
Welcome back to another exploration of Go! This week, we're looking at Go’s three container types: arrays, slices, and maps.
So far, things have been quite easy to follow, but these examples are getting longer and denser. Let’s dive in and make sense of it all.
Arrays
In Go, an array
is a sequence of elements with a fixed length. Each element must be of the same type.
var a [5]int
Here, we declare an array with a length of 5
, meaning it will always have exactly five elements.
a[4] = 100
We can modify a specific index of an array using square brackets. Here, we’re setting the value at the fourth position (index 4
).
fmt.Println("len:", len(a))
To get the length of an array, we use the built-in len
function.
b := [5]int{1, 2, 3, 4, 5}
We can initialise an array while declaring it using curly braces.
b := [...]int{1, 2, 3, 4, 5}
By using [...]
, we allow Go to infer the length based on the initial values.
b := [...]int{100, 3: 400, 500}
Interestingly, we can also specify an index while declaring values. Here, b[3]
is 400
, and b[4]
is 500
, leaving b[1]
and b[2]
as zero values.
Multi-Dimensional Arrays
var twoD [2][3]int
for i := 0; i < 2; i++ {
for j := 0; j < 3; j++ {
twoD[i][j] = i + j
}
}
Go supports multi-dimensional arrays. Here, we create a 2×3
array and populate it using nested loops.
twoD := [2][3]int{
{1, 2, 3},
{4, 5, 6},
}
Alternatively, we can declare and initialise a multi-dimensional array directly.
Slices
Slices and arrays have a lot in common, but one key difference is that a slice
doesn't require its length to be known at build time, it can grow dynamically.
var s []string
This looks familiar from the array
example, except we’re not declaring the length. The s
slice starts with a length of 0
and is equal to nil
.
s = make([]string, 3)
s[0] = "a"
s[1] = "b"
s[2] = "c"
Here, we use the make
function to create a slice
with an initial length of 3
. Each index starts with a zero-valued string, and we can then assign values like a regular array.
NOTE: We can give a
slice
a capacity, which is its maximum length. By default, this is the same as the length passed intomake
, but if we pass in a second number, we can change it.
s = append(s, "d")
s = append(s, "e", "f")
The append
function allows us to expand a slice
by adding new elements at the end.
c := make([]string, len(s))
copy(c, s)
Here, we create a new slice
of the same length as s
using make
and copy the contents over using copy
.
l := s[2:5]
l = s[:5]
l = s[2:]
Slices support the slice operator [:]
, which allows extracting a portion of the slice between two indexes.
-
s[2:5]
gives elements from index2
to4
(index5
is excluded). -
s[:5]
is equivalent tos[0:5]
. -
s[2:]
is equivalent tos[2:len(s)]
.
t := []string{"g", "h", "i"}
t2 := []string{"g", "h", "i"}
if slices.Equal(t, t2) {
fmt.Println("t == t2")
}
We imported the slices package at the beginning, which allows us to compare slices using slices.Equal
.
twoD := make([][]int, 3)
for i := 0; i < 3; i++ {
innerLen := i + 1
twoD[i] = make([]int, innerLen)
for j := 0; j < innerLen; j++ {
twoD[i][j] = i + j
}
}
fmt.Println("2d: ", twoD)
This took a minute to get my head around, so I'll break it down:
- We create a
slice
with 3 inner slices ([[], [], []]
). - Each inner slice is initialised with a length of
i + 1
([[int], [int, int], [int, int, int]]
). - Finally, we populate each inner slice by adding
i + j
at each position ([[0], [1, 2], [2, 3, 4]]
).
Maps
Let's move on to maps. You might know them as hashes, dicts, or objects in other languages, but here they're called maps. They allow you to have a key/value pair.
m := make(map[string]int)
Declaring a map is similar, but this time we place the key type in square brackets [string]
and the value type outside int
.
m["k1"] = 7
m["k2"] = 13
We set values the same way as an array
or slice
, but this time the keys are strings rather than indexes.
v1 := m["k1"]
v3 := m["k3"]
We can retrieve values the same way we set them. If we attempt to read a key that hasn't been set, we get a zero-valued
response instead of an error.
fmt.Println("len:", len(m))
The len
function still works here and will return how many keys we have set.
delete(m, "k2")
clear(m)
We have a nice delete
function that removes a single key and its value, and a clear
function that removes all key-value pairs.
_, prs := m["k2"]
When reading a value from a map
, we receive two values. The first is the stored value (or a zero-valued
response if the key is missing). The second is a boolean indicating whether the key was present in the map.
n := map[string]int{"foo": 1, "bar": 2}
n2 := map[string]int{"foo": 1, "bar": 2}
if maps.Equal(n, n2) {
fmt.Println("n == n2")
}
Again, in this example, we've imported another package, maps, which lets us check if two maps are equivalent.
Wrapping Up
OK, it feels like we're starting to get somewhere now. We're actually learning things that could be useful in real-world applications. Next time, we'll look at functions and what we can do with them.
Thanks for reading! If you have any questions, leave a comment. I'm also considering writing a simple script, let me know if you have any ideas!
If you’d like to connect, here are my Twitter, BlueSky, and LinkedIn profiles. Come say hi 😊.
This content originally appeared on DEV Community and was authored by Andrew Bone

Andrew Bone | Sciencx (2025-03-27T16:30:00+00:00) Learning GO: The container types. Retrieved from https://www.scien.cx/2025/03/27/learning-go-the-container-types/
Please log in to upload a file.
There are no updates yet.
Click the Upload button above to add an update.