Level 5 /

Collections

The project is growing. Gopher needs to track inventory: lists of materials, maps of which supplies go where, manifests of which residents have which passes. Inside the warehouse next to the tracks, crates are stacked on shelves and shipping manifests fill the clipboards.

Gopher says

Time to organize passengers! Go has two main collection types: slices (ordered lists that grow dynamically) and maps (key-value lookups like dictionaries). baton-junction uses both in nearly every function.

In baton-junction, every List method builds a []*v2.Resource slice with append, and we use token maps to track pagination cursor state across pages.

Slices

A slice is a dynamic-length, ordered sequence. Declare one with var, then grow it with append:

var resources []*v2.Resource
for _, u := range users {
    r, err := userResource(&u)
    if err != nil {
        return nil, "", nil, err
    }
    resources = append(resources, r)
}

This is the declare-append pattern - it appears in every List method in baton-junction.

Key Slice Operations

The most important thing to remember about append is that it returns a new slice. Here’s what the syntax looks like:

must reassign resultbuilt-in functionexisting slicenew element
resources = append(resources, r)

append returns a new slice - you must reassign the result. This is the #1 beginner mistake.

Understanding nil Across Types

nil means “no value” in Go, but it behaves differently depending on the type. This is one of the trickier parts of Go. A nil slice is safe to use, but a nil map will crash if you try to write to it:

keywordnil-safe (read OK, append OK)nil-dangerous (write panics)nil means absent
var s []string
var m map[string]int
var p *User

Nil Slices Are Safe

One nice thing about Go slices: you don’t need to initialize them before using them. A nil slice (one that hasn’t been assigned yet) works just fine with len, append, and for range:

var s []string        // nil  -  s == nil is true
len(s)                // 0
append(s, "hello")    // works  -  allocates and returns a one-element slice
for _, v := range s { // zero iterations
}

Slice Literals

Sometimes you already know exactly what goes in the slice at the time you write it. Instead of starting empty and appending, you can declare all the elements inline:

element typeliteral values
[]Type{
    value1,
    value2,
    value3,
}
return []connectorbuilder.ResourceSyncerV2{
    newUserBuilder(m.client),
    newGroupBuilder(m.client),
    newRoleBuilder(m.client),
}

Iterating with for range

Most of the time you won’t know what’s in a slice ahead of time. You’ll get data from an API, a database, or some other source and need to loop through it. Go’s for range is how you walk through every element in a slice (or a map):

keywordindex (discarded)element valuecollection
for _, u := range users {
    ...
}
for _, u := range users {
    r, err := userResource(&u)
    resources = append(resources, r)
}

Each iteration yields an index and an element. The _ discards the index (Go requires you to use every declared variable).

Maps

Slices are great when you care about order, but sometimes you need to look something up by a key instead of by position. That’s what maps are for. A map is an unordered collection of key-value pairs, like a dictionary in Python or an object in JavaScript.

keywordkey typevalue typekey-value pairs
map[KeyType]ValueType{
    key1: value1,
    key2: value2,
}

Here are three ways to create them:

// Literal  -  known contents
profile := map[string]interface{}{
    "first_name": u.FirstName,
    "last_name":  u.LastName,
}

// make  -  populate later
attrs := make(map[string]string)

// Package-level lookup table
var attrsLookup = map[string]string{
    "first_name": "first_name",
    "email":      "email",
}

Writing to a nil map panics. Always initialize with make or a literal before writing.

Maps with any Values

In the map literal above, map[string]interface{} (or its alias map[string]any) means “a map where keys are strings and values can be anything.” You’ll see this when building JSON-like structures with mixed value types.

Dereferencing Pointers

You may notice *u.LastLogin in the conditional-append section below. The * in front of a pointer variable means “get the actual value at this pointer.” If u.LastLogin is a *time.Time (pointer to a Time), then *u.LastLogin gives you the time.Time value itself. Always check for nil first to avoid a panic:

if u.LastLogin != nil {
    opts = append(opts, resource.WithLastLogin(*u.LastLogin))
}

The Comma-Ok Pattern

When you read from a map, you might get a zero value back. But does that mean the key exists with a zero value, or that it doesn’t exist at all? Go solves this with the “comma-ok” pattern, which gives you a second boolean return value: true if the key exists, false if it doesn’t:

value, ok := myMap["key"]
if ok {
    // key exists, value is the real value
}
// if ok is false, value is the zero value for that type

This same pattern works with type assertions (v, ok := x.(T)) and channel receives. The ok boolean eliminates ambiguity between “key exists with zero value” and “key doesn’t exist.”

Iterating Maps

Just like slices, you can loop through maps with for range. Each iteration gives you a key and a value:

for c1Name, apiName := range attrsLookup {
    val, found := actions.GetStringArg(args, c1Name)
    if found && val != "" {
        attrs[apiName] = val
    }
}

Map iteration order is not guaranteed - Go deliberately randomizes it.

Quick Check

What happens if you write to a nil map in Go? (one word)

Exercise: Transform and Lookup

Implement two functions. activeEmails should return a slice of email addresses for users whose status is "active". emailLookup should return a map from email address to user pointer.

Gopher says

Slices and maps are Go’s workhorses. The declare-append loop and comma-ok pattern will become second nature.

In baton-junction, you’ll see var resources []*v2.Resource followed by an append loop in every builder’s List method - it’s the most common collection pattern in the codebase.

Next stop: packages and visibility!

Next Lesson

Packages & Visibility

Continue