Hey there, and welcome back to our Go learning journey! 👋
In the last chapter, we explored how to write logic using if, for, and switch. Now it’s time to level up and introduce a powerful idea in Go: composite data types.
These are essential when simple types like int and string just aren’t enough. Let’s dig in together.
What Are Composite Data Types?
Composite data types allow us to group multiple values into a single structure. Think of it like this: instead of tracking individual numbers or strings, you can combine them in one variable.
In Go, the key composite types we’ll cover are:
- Arrays
- Slices
- Map
- Struct
Arrays — Fixed-Size Containers
An array is a collection of values, all of the same type, and it has a fixed size defined at compile time.
Declaring an Array
var x [5]int
This creates an array x with 5 integers, all initialized to 0 (Go auto-initializes!).
Accessing Elements
x[0] = 2
fmt.Println(x[1]) // prints 0, since we didn’t set it
Arrays are zero-indexed, so x[0] is the first element.
Want to initialize an array with specific values?
x := [5]int{1, 2, 3, 4, 5}
You can also let Go infer the size using ...:
x := [...]int{1, 2, 3, 4}
- The [...] tells Go: “Figure out how many elements I’m giving you, and make the array that size.”
- The type of x is [4]int — a fixed-length array of 4 integers.
- Arrays cannot change size once declared.
Looping Through Arrays
Iterating is super common. Go makes it clean with range:
arr := [3]int{1, 2, 3}
for i, v := range arr {
fmt.Println(i, v)
}
- i is the index
- v is the value at that index
Slices — Flexible and Powerful
Arrays are cool, but their size is fixed. Slices are like dynamic arrays—much more common and practical in Go.
Creating a Slice from an Array
arr := [7]string{"a", "b", "c", "d", "e", "f", "g"}
s1 := arr[1:3] // includes index 1 and 2 ("b", "c")
s2 := arr[2:5] // includes "c", "d", "e"
Slices are windows into arrays. They can overlap and share data.
Length and Capacity
Every slice has:
- len(slice): number of elements
- cap(slice): total capacity (how far it can grow without reallocating)
fmt.Println(len(s1), cap(s1)) // Output: 2 6
You can declare a slice directly:
s := []int{10, 20, 30}
This automatically creates the underlying array and slice together.
Creating Slices with make
Use make() to create a slice with a specific size (and optionally, capacity):
s1 := make([]int, 5) // length and capacity = 5
s2 := make([]int, 3, 10) // length = 3, capacity = 10
Growing Slices with append
Need to add elements? Use append:
s := []int{}
s = append(s, 1)
s = append(s, 2, 3)
Behind the scenes, Go grows the slice and manages memory for you. If the capacity is exceeded, it allocates a larger array and copies the data—seamlessly.
Shared Underlying Arrays
When you slice an array or another slice, the new slice shares the same memory.
So changing one affects the other:
arr := [5]int{1, 2, 3, 4, 5}
s1 := arr[1:4] // {2, 3, 4}
s2 := arr[2:5] // {3, 4, 5}
s1[1] = 99 // modifies arr[2]
s2[0] = 88 // modifies arr[2]
fmt.Println(arr, s1, s2) // Output: [1 2 88 4 5] [2 88 4] [88 4 5]
Understanding this is key to avoiding subtle bugs.
What is a Map in Go?
A map in Go is Go’s version of a hash table, a data structure that stores key-value pairs. It’s like a dictionary where you look things up by a name (the key) instead of by a number.
Think of a map where the key is someone’s name and the value is their phone number:
idMap := map[string]int{
"Joe": 123,
"Jane": 456,
}
You can access values by key:
fmt.Println(idMap["Joe"]) // Outputs: 123
Using make:
var idMap map[string]int
idMap = make(map[string]int)
This creates an empty map where:
- The key is a string
- The value is an int
Using a map literal:
idMap := map[string]int{
"Joe": 123,
"Jane": 456,
}
Adding or Updating Values
idMap["Pat"] = 789 // Add a new pair
idMap["Jane"] = 999 // Update an existing value
Deleting a Key
delete(idMap, "Joe") // Removes the key "Joe" and its value
Checking if a Key Exists
id, ok := idMap["Joe"]
if ok {
fmt.Println("Joe's ID is", id)
} else {
fmt.Println("Joe not found.")
}
- ok is true if the key exists.
- This is useful to avoid getting default zero-values for non-existing keys.
Looping Through Maps
Use range to iterate over every key-value pair:
for key, val := range idMap {
fmt.Println(key, val)
}
Maps don’t have a guaranteed order, so the output order may vary.
What is a Struct?
A struct (short for structure) lets you group multiple related values—of possibly different types—into a single object.
It’s like a lightweight class in other languages, and perfect when you want to model something meaningful—like a Person.
Declaring a Struct:
type Person struct {
name string
address string
phone string
}
Creating Struct Instances
You can create and initialize a struct in a few ways.
- Using var and dot notation:
var p1 Person
p1.name = "Joe"
p1.address = "123 Main St"
p1.phone = "555-1234"
- Using a struct literal:
p2 := Person{
name: "Jane",
address: "456 Oak Ave",
phone: "555-6789",
}
Accessing Fields
You can read and write to fields using dot notation:
fmt.Println(p2.name) // Read
p2.phone = "555-0000" // Write
Using new for Initialization
p3 := new(Person)
p3.name = "Pat" // All fields are initialized to zero-values by default
This allocates a pointer to a Person struct.
You can even combine them! For example:
people := map[string]Person{
"joe": {name: "Joe", address: "Street 1", phone: "123"},
}
You’re doing great. Keep going, and don’t hesitate to experiment in your own Go playground. Remember: every great Go programmer started right where you are.
See you in the next one! 🧑💻✨
Top comments (0)