Go provides a small set of built-in data structures that are incredibly powerful when used well. Arrays are fixed-length collections of elements. Slices build on arrays, offering flexible, dynamic-sized views. Maps are key-value stores that enable fast lookups and efficient data organization. Together, these types cover most common use cases — from simple lists to complex indexed data.
- Arrays are value types.
- Size is part of the type: [3]int is not the same as [4]int.
- Copying an array creates a new copy of all elements.
var arr [3]int = [3]int{1, 2, 3}
fmt.Println(arr[0]) // 1- Slices are reference types — they point to an underlying array.
- Slices are dynamic sizing.
- A slice is a small struct with:
type slice struct {
ptr *T // pointer to the first element
len int // number of elements
cap int // capacity (length of underlying array)
}s := []int{1, 2, 3} // slice with len=3, cap=3
fmt.Println(s[1]) // 2When we're adding the values to the Slice that have no predefine with size, we will using append to add value to it. While there's memory behaviour of append when work with it and might be having memory/performance issues.
When you append to a slice, Go checks the capacity:
- If there's room in the underlying array (len < cap), it reuses the array.
- If there's no room, it:
- Allocates a new underlying array, typically with double capacity (but implementation may vary)
- Copies existing values to the new array
- Returns a new slice backed by that new array
func main() {
s := []int{1, 2, 3}
fmt.Println(len(s), cap(s)) // 3 3
s = append(s, 4) // triggers new allocation
fmt.Println(len(s), cap(s)) // 4 6 (capacity may double)
}Because append might return a new underlying array, you must capture the result:
s := []int{1, 2, 3}
append(s, 4)
fmt.Println(s) // [1 2 3]
s = append(s, 4)
fmt.Println(s) // [1 2 3 4]Based on this example, you can see if we didn't set the result from the append, it's won't update and also meaning when we're doing append actually underlying it's copies a new set of data over and if there's a large list and we using append it's will causing a lot of the memory allocation also slower performance.
So for the scenario we will do Pre-Allocating Slices instead which come with predefine capacity and avoid repeated allocations:
s := make([]int, 0, 10) // len=0, cap=10This is ideal when you're building up a large list and want to minimize reallocation.
Slices created from the same array share data:
a := [5]int{1, 2, 3, 4, 5}
s1 := a[0:3]
s2 := a[1:4]
s1[1] = 100
fmt.Println(s2) // [100 3 4]A map is Go's built-in hash table — a powerful data structure that lets you associate keys with values. They're fast, flexible, and a common choice for storing dynamic or lookup-style data. Maps key can be any type that's are comparable string, int etc. but not Slices and for value can be any type.
Maps are also references type which similar with Slice, when you're assign or pass an map, it still refers to the same underlying data.
func update(m map[string]int) {
m["new"] = 1
}
m := map[string]int{}
update(m)
fmt.Println(m) // map[new:1]Zero values of a map is nil, while you can initialize it with make(map[string]string). Without intialize, you will still able to read without issues but when writing it will be panic. Example
var m map[string]int
fmt.Println(m["x"]) // 0
m["x"] = 1 // panic with assignment to entry in nil mapThere's few syntax which allow you to access or modify the maps easier.
- Key Access & Check Existance
value, ok := m["apple"]
if ok {
fmt.Println("Found:", value)
} else {
fmt.Println("Not found")
}- Deleting Key
delete(m, "banana")- Iterating Over Maps
Map iteration order is randomized for safety (to avoid relying on order).
for key, value := range m {
fmt.Printf("%s -> %d\n", key, value)
}