DEV Community

Cover image for GoLang 101: Organizing Data in Go with Arrays, Slices, Maps, and Structs
Kazem
Kazem

Posted on

GoLang 101: Organizing Data in Go with Arrays, Slices, Maps, and Structs

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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}
Enter fullscreen mode Exit fullscreen mode

You can also let Go infer the size using ...:

x := [...]int{1, 2, 3, 4}
Enter fullscreen mode Exit fullscreen mode
  • 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)
}
Enter fullscreen mode Exit fullscreen mode
  • 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"
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

You can declare a slice directly:

s := []int{10, 20, 30}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Growing Slices with append

Need to add elements? Use append:

s := []int{}
s = append(s, 1)
s = append(s, 2, 3)
Enter fullscreen mode Exit fullscreen mode

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]
Enter fullscreen mode Exit fullscreen mode

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,
}
Enter fullscreen mode Exit fullscreen mode

You can access values by key:

fmt.Println(idMap["Joe"]) // Outputs: 123
Enter fullscreen mode Exit fullscreen mode

Using make:

var idMap map[string]int
idMap = make(map[string]int)
Enter fullscreen mode Exit fullscreen mode

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,
}
Enter fullscreen mode Exit fullscreen mode

Adding or Updating Values

idMap["Pat"] = 789       // Add a new pair
idMap["Jane"] = 999      // Update an existing value
Enter fullscreen mode Exit fullscreen mode

Deleting a Key

delete(idMap, "Joe") // Removes the key "Joe" and its value
Enter fullscreen mode Exit fullscreen mode

Checking if a Key Exists

id, ok := idMap["Joe"]
if ok {
    fmt.Println("Joe's ID is", id)
} else {
    fmt.Println("Joe not found.")
}
Enter fullscreen mode Exit fullscreen mode
  • 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)
}
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

Creating Struct Instances

You can create and initialize a struct in a few ways.

  1. Using var and dot notation:
var p1 Person
p1.name = "Joe"
p1.address = "123 Main St"
p1.phone = "555-1234"
Enter fullscreen mode Exit fullscreen mode
  1. Using a struct literal:
p2 := Person{
    name:    "Jane",
    address: "456 Oak Ave",
    phone:   "555-6789",
}
Enter fullscreen mode Exit fullscreen mode

Accessing Fields

You can read and write to fields using dot notation:

fmt.Println(p2.name)       // Read
p2.phone = "555-0000"      // Write
Enter fullscreen mode Exit fullscreen mode

Using new for Initialization

p3 := new(Person)
p3.name = "Pat" // All fields are initialized to zero-values by default
Enter fullscreen mode Exit fullscreen mode

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"},
}
Enter fullscreen mode Exit fullscreen mode

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)