Notes — Module 4: Composite Types¶
These are your personal study notes. Write freely and honestly. Incomplete notes are fine — they show where your understanding still needs work. Return to this file to add insights as they develop over time.
Module: [[go/4. Composite Types]] Topic: [[go]] Date started: YYYY-MM-DD Status: In progress
Concept Map¶
Sketch how the concepts in this module relate to each other. Fill in the Mermaid diagram.
mindmap
root((Composite Types))
Arrays
fixed size part of type
value semantics full copy
backing store for slices
Slices
three-word header ptr len cap
sharing and aliasing
append may reallocate
copy for independent clones
nil vs empty
three-index slice expression
slices package 1.21
Maps
hash table unordered
nil map panics on write
comma-ok idiom
delete
iteration order randomized
maps as sets struct{}
maps of slices
maps package 1.21
concurrency caveat
Strings
immutable byte slice
rune vs byte
string to byte to rune
strings.Builder
Structs
value semantics
named literals vs positional
embedding promotes fields
comparable if all fields comparable
empty struct zero bytes
struct tags for reflection
memory layout and alignment
Connections
Links to go/5 Pointers
Links to go/6 Methods and Interfaces
Links to memory-management
Alternative: draw this on paper, photo it, and link the image here.
Key Insights¶
The "aha moments" — the things that, once understood, made the rest clear. Be specific: "I finally understood X because Y" is more useful than "X makes sense".
- Slices are headers, not data: I finally understood that assigning a slice copies the three-word header (ptr, len, cap), not the underlying array. Two slice variables can point into the same array without either "knowing" about the other. This is why
appendmust return a value — it may have changed the ptr. - nil map reads safely, writes panic: A nil map is not like a null pointer — reads return the zero value without panic. Only writes panic. Knowing this lets me read from optional maps safely and reliably diagnose the "assignment to entry in nil map" panic.
- Add insights as you discover them
My Understanding¶
Explain the core concepts in your own words, as if teaching them to someone else. If you can't explain it simply, you don't understand it well enough yet.
Arrays¶
Your explanation here
What I'm still unsure about: (e.g., when does the compiler allocate an array on the stack vs heap?)
Slices (the header)¶
Your explanation here
What I'm still unsure about: (e.g., exactly how the growth factor changes for large slices)
append and reallocation¶
Your explanation here
What I'm still unsure about: (e.g., what happens to the old backing array after reallocation — when does it get GC'd?)
Maps¶
Your explanation here
What I'm still unsure about: (e.g., how does Go implement hash randomization for map iteration?)
Structs and embedding¶
Your explanation here
What I'm still unsure about: (e.g., what happens when two embedded types both have a field with the same name?)
Connections to Other Topics¶
How does this module connect to things you already know?
| This module's concept | Connects to | How |
|---|---|---|
| Slice value semantics | [[go/5. Pointers]] | Passing a *[]int to a function lets the function modify the caller's slice header; this is the pointer side of the same coin |
| Struct fields and embedding | [[go/6. Methods and Interfaces]] | Methods are defined on types; embedding promotes not just fields but also methods, enabling interface satisfaction through composition |
| Maps are not concurrent-safe | [[go/9. Concurrency]] | Any map accessed from multiple goroutines needs a mutex or sync.Map; this module introduces the footgun, Module 9 fixes it |
| Backing array lifecycle | [[memory-management]] | The GC manages backing arrays; escape analysis determines whether a slice's backing array lives on the stack or heap |
Questions That Arose¶
Log questions as they appear. Don't stop to answer them now — just capture them. Then move the serious ones to QUESTIONS.md.
- When two embedded types both have a field with the same name, what does Go do? → added to QUESTIONS.md as Q001
- After
appendcauses a reallocation, when does the old backing array get garbage collected? → might be answered in memory-management module - Can you use a struct as a map key if the struct contains an array field? → add to QUESTIONS.md as Q002
Code Snippets Worth Remembering¶
Patterns, idioms, or examples that captured something important.
The safe slice copy pattern¶
// When you need an independent copy of a slice, never do: clone := s
// Instead:
clone := make([]int, len(s))
copy(clone, s)
// Or with the slices package (Go 1.21+):
clone2 := slices.Clone(s)
Why I'm saving this: The plain assignment clone := s copies the header only. Mutations to clone elements will silently affect s and vice versa. copy is the safe alternative.
Maps as sets¶
// Set with struct{} (zero bytes)
seen := make(map[string]struct{})
seen["apple"] = struct{}{}
_, ok := seen["apple"] // ok = true if present
Why I'm saving this: This pattern comes up constantly for deduplication, visited-node tracking in graph traversal, and indexing. The struct{}{} syntax looks weird but struct{} is the zero-byte type.
The comma-ok idiom¶
if v, ok := m[key]; ok {
// key exists; v is the value
} else {
// key not present; v is the zero value
}
Why I'm saving this: Without comma-ok, a missing key and a key with zero value are indistinguishable. This is the canonical safe map read.
maps of slices (append-to-nil pattern)¶
byKey := make(map[string][]string)
byKey["a"] = append(byKey["a"], "apple") // nil + append = new slice
byKey["a"] = append(byKey["a"], "avocado")
// Works because a missing map key returns nil, and append(nil, x) works
Why I'm saving this: Idiomatic grouping pattern. No initialization of the inner slice is required — reading a missing key returns nil, and append(nil, x) correctly creates a new slice.
Three-index slice (capacity-limiting sub-slice)¶
// Limit capacity to prevent sub-slice from clobbering the original
safe := full[low:high:high] // cap == high - low == len
// Now append to safe will always reallocate, never touch full[high:]
Why I'm saving this: This is the defensive form when passing a sub-slice to a function that might append to it. Without the third index, the sub-slice has extra capacity into the parent's elements.
What Tripped Me Up¶
Mistakes I made, misconceptions I had, things that confused me more than they should have. Being honest here helps you later.
- append must always be assigned back — I initially thought
append(s, x)modifiedsin place. It doesn't — it returns a (possibly different) slice that must be assigned back:s = append(s, x). The compiler doesn't warn you if you forget the assignment. - Sub-slice aliasing — I initially thought
sub := s[1:3]gave me a new, independent slice. It shares the backing array. Modifyingsub[0]modifiess[1].
Summary in My Own Words¶
Write a 3–5 sentence summary of this entire module without looking at any notes. If you can't do this, you need more study time.
Write your summary here after completing the module.
Last updated: YYYY-MM-DD