In this lesson we cover Go arrays, slices, and maps — the three main ways to store collections of data. If you completed Go Functions, you already know how to pass values around. Now you will learn how to group them into ordered lists and key-value lookups. This Go slices tutorial focuses on slices because they are the collection type you will use most often in real programs.
Prerequisites: Lessons 1–6 completed, Go 1.22 or newer installed. Estimated time: 45–60 minutes.
Go Programming Tutorial Series Overview
Follow these twelve lessons in order. Each includes runnable code and clear explanations:
| Lesson | Topic | Link |
|---|---|---|
| 1 | Go Programming Introduction | Lesson 1 |
| 2 | Go Programming Setup | Lesson 2 |
| 3 | Go Basic Syntax | Lesson 3 |
| 4 | Go Variables and Types | Lesson 4 |
| 5 | Go Control Flow | Lesson 5 |
| 6 | Go Functions | Lesson 6 |
| 7 | Go Arrays, Slices, and Maps (this lesson) | You are here |
| 8 | Go Structs and Methods | Lesson 8 |
| 9 | Go Pointers | Lesson 9 |
| 10 | Go Interfaces and Errors | Lesson 10 |
| 11 | Go Goroutines and Channels | Lesson 11 |
| 12 | Go HTTP REST API | Lesson 12 |
1. Arrays — Fixed-Size Collections
An array in Go is a sequence of elements of the same type with a fixed length determined at compile time. The length is part of the array’s type — [3]int and [5]int are different types and cannot be assigned to each other.
Declare an array by specifying the length and type, then provide values in curly braces:
package main
import "fmt"
func main() {
// Array of 5 integers, all zero-initialized
var scores [5]int
// Array with literal values
days := [7]string{"Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"}
// Let the compiler count elements with ...
primes := [...]int{2, 3, 5, 7, 11}
scores[0] = 95
fmt.Println(scores[0]) // 95
fmt.Println(len(days)) // 7
fmt.Println(len(primes)) // 5
}
Arrays are useful when you know the exact size upfront — for example, the seven days of the week or a fixed-size buffer. In most application code, however, you need collections that can grow and shrink. That is where slices come in.
2. Slices — Dynamic Views Over Arrays
A slice is a flexible, dynamically sized view into an underlying array. Slices are reference types: assigning one slice variable to another copies the slice header (pointer, length, capacity) but not the elements themselves. This is why slices appear everywhere in Go — from parsing HTTP requests to processing JSON.
You can create a slice in several ways:
- Slice literal:
nums := []int{1, 2, 3} - From an array:
arr := [5]int{10, 20, 30, 40, 50}; s := arr[1:4]— elements at indices 1, 2, 3 - With make:
make([]int, length, capacity)
package main
import "fmt"
func main() {
// Slice literal
fruits := []string{"apple", "banana", "cherry"}
// make(type, length, capacity)
// length = number of accessible elements
// capacity = total space in the backing array
buffer := make([]byte, 0, 16)
fmt.Println(fruits) // [apple banana cherry]
fmt.Println(len(buffer)) // 0
fmt.Println(cap(buffer)) // 16
}
3. append, len, and cap
Three functions and built-ins define everyday slice work:
len(s)— returns the number of elements currently in the slicecap(s)— returns the capacity of the underlying array from the slice’s start indexappend(s, elements...)— adds elements to the end, growing the slice when needed
When append exceeds capacity, Go allocates a new, larger backing array (typically doubling capacity), copies existing elements, and returns the updated slice. Always assign the result back to your variable:
package main
import "fmt"
func main() {
nums := []int{1, 2, 3}
fmt.Printf("len=%d cap=%d %v\n", len(nums), cap(nums), nums)
nums = append(nums, 4, 5, 6)
fmt.Printf("len=%d cap=%d %v\n", len(nums), cap(nums), nums)
// append can add another slice with ...
more := []int{7, 8}
nums = append(nums, more...)
fmt.Printf("len=%d cap=%d %v\n", len(nums), cap(nums), nums)
}
Understanding len vs cap helps you write efficient code. Pre-allocating with make([]T, 0, expectedSize) avoids repeated reallocations when you know roughly how many elements you will store — a common pattern in API handlers and data pipelines.
4. Slicing, Copying, and Common Patterns
You can create sub-slices with the syntax s[low:high]. Omitting low defaults to 0; omitting high defaults to len(s):
s := []int{10, 20, 30, 40, 50}
first := s[:2] // [10 20]
middle := s[1:4] // [20 30 40]
last := s[3:] // [40 50]
To copy elements without sharing the backing array, use the built-in copy function:
src := []int{1, 2, 3}
dst := make([]int, len(src))
copy(dst, src)
A nil slice has len 0 and cap 0. You can call append on a nil slice — it works fine. An empty slice literal []int{} is also valid and sometimes preferred when serializing to JSON, because null vs [] matters for API consumers.
5. Maps — Key-Value Collections
A map associates keys with values. Both keys and values have types — for example, map[string]int maps string keys to integer values. Maps are reference types, like slices.
Create a map with make or a literal:
package main
import "fmt"
func main() {
// Map literal
ages := map[string]int{
"Alice": 30,
"Bob": 25,
}
// make(map[KeyType]ValueType)
scores := make(map[string]int)
scores["math"] = 92
scores["science"] = 88
fmt.Println(ages["Alice"]) // 30
fmt.Println(scores) // map[math:92 science:88]
}
Reading a missing key returns the zero value for the value type (0 for int, "" for string). To distinguish “key not found” from “key exists with zero value”, use the two-value form:
score, ok := scores["history"]
if !ok {
fmt.Println("history score not found")
} else {
fmt.Println("history:", score)
}
Delete a key with delete(scores, "math"). Maps are not safe for concurrent access from multiple goroutines without synchronization — you will learn about that in the goroutines lesson.
6. Iteration with range
The range keyword iterates over slices, arrays, maps, strings, and channels. For slices and arrays, range provides the index and value:
package main
import "fmt"
func main() {
colors := []string{"red", "green", "blue"}
// Index and value
for i, color := range colors {
fmt.Printf("%d: %s\n", i, color)
}
// Value only — use _ to ignore the index
for _, color := range colors {
fmt.Println(color)
}
// Map iteration — key and value
capital := map[string]string{
"Nigeria": "Abuja",
"Kenya": "Nairobi",
"Ghana": "Accra",
}
for country, city := range capital {
fmt.Printf("%s → %s\n", country, city)
}
}
Map iteration order is randomized in Go — do not depend on any particular order. If you need sorted output, collect keys into a slice and sort them first (using the sort package).
7. Arrays vs Slices vs Maps — Quick Reference
| Type | Size | Access | Typical Use |
|---|---|---|---|
| Array | Fixed at compile time | Index [i] |
Fixed buffers, low-level code |
| Slice | Dynamic (append) |
Index [i] |
Lists, sequences, most collections |
| Map | Dynamic (grows automatically) | Key [key] |
Lookups, counting, grouping |
8. Next Steps
You now understand Go arrays, slices, and maps — how to create them with literals and make, grow slices with append, inspect size with len and cap, and iterate with range. In the next lesson we cover structs and methods — Go’s way to group related data and attach behavior to types.
Continue the series: Lesson 6 – Functions · Lesson 8 – Structs and Methods
Frequently Asked Questions
What is the difference between arrays and slices in Go?
Arrays have a fixed size that is part of their type. Slices are dynamic views backed by an array and are used far more often in real Go programs.
How do you append to a slice in Go?
Use append(slice, element) and assign the result back: slice = append(slice, value). When capacity is exceeded, Go allocates a new backing array automatically.
Are Go maps ordered?
No. Map iteration order is randomized. Sort keys separately if you need a consistent order in output.
Want live Go or backend classes? Join Alkademy for instructor-led programming courses with hands-on projects.