🎯 Goal
Understand how Go handles slices, maps, pointers, and memory so that you can:
- Avoid subtle bugs
- Write predictable code
- Understand value vs reference semantics
- Avoid
nil-related panics - Make informed design decisions
This chapter is where Go starts to feel different from Python.
1️⃣ Arrays vs Slices – The Foundation
1.1 Arrays in Go
An array in Go has:
- Fixed size
- Size as part of its type
- Value semantics
Example:
var numbers [3]int
Important:
[3]int != [4]int
They are different types.
Arrays are Values
If you pass an array to a function:
func modify(arr [3]int) {
arr[0] = 100
}
The original array will NOT change.
Why?
Because arrays are copied when passed.
This is different from Python lists, which are references.
1.2 Why Arrays Are Rarely Used
In production Go:
- Arrays are mostly used internally
- Slices are used almost everywhere
Arrays exist mainly because:
- They are part of Go’s type system
- Slices are built on top of them
2️⃣ Slices – The Real Collection Type
Slices are:
- Dynamic
- Flexible
- Backed by arrays
- Reference-like (but not fully references)
2.1 What Is a Slice?
A slice is a small struct:
It contains:
- Pointer to an underlying array
- Length
- Capacity
Conceptually:
slice -> { ptr, len, cap }
2.2 Creating Slices
Literal
nums := []int{1, 2, 3}
Using make
nums := make([]int, 3)
This creates:
- length = 3
- capacity = 3
- all values initialized to zero
2.3 Length vs Capacity
nums := make([]int, 3, 5)
- len = 3
- cap = 5
Capacity defines how much it can grow before allocating new memory.
This matters for performance.
2.4 Append Behavior
nums := []int{1, 2}
nums = append(nums, 3)
If capacity is exceeded:
- Go allocates new array
- Copies old data
- Updates pointer
This is why modifying slices can be tricky.
2.5 Slice Sharing Pitfall
Example:
a := []int{1, 2, 3, 4}
b := a[:2]
b[0] = 100
Now a becomes:
[100 2 3 4]
Because both share the same underlying array.
This surprises many developers.
2.6 Safe Copying
To avoid shared-memory issues:
copySlice := make([]int, len(original))
copy(copySlice, original)
Now they are independent.
3️⃣ Maps – Reference Types with Rules
Maps in Go are:
- Built-in reference types
- Must be initialized before use
- Not thread-safe
3.1 Creating Maps
Literal
m := map[string]int{
"a": 1,
}
Using make
m := make(map[string]int)
3.2 Zero Value of Map
var m map[string]int
This creates:
m == nil
Trying to assign:
m["a"] = 1
Will panic.
Why?
Because the map is nil.
You must initialize with make.
3.3 Reading From Map
Reading from a nil map is safe:
value := m["a"] // returns zero value
But writing is not.
3.4 Checking Existence
value, ok := m["a"]
This is the correct pattern.
Never assume key existence.
3.5 Maps Are Reference Types
When you pass a map to a function:
- It is not copied
- Changes affect original
Example:
func update(m map[string]int) {
m["x"] = 10
}
Original map is modified.
This is different from arrays.
4️⃣ Pointers – Controlled Memory Access
Go does not allow pointer arithmetic.
Pointers are safe and restricted.
4.1 Basic Pointer
x := 10
p := &x
&gives address*pdereferences
4.2 Why Use Pointers?
Three main reasons:
- Avoid copying large structs
- Modify original data
- Indicate optional values
4.3 Structs and Pointers
Example:
type User struct {
Name string
}
Value receiver:
func (u User) UpdateName(name string) {
u.Name = name
}
This does NOT modify original.
Pointer receiver:
func (u *User) UpdateName(name string) {
u.Name = name
}
This modifies original.
4.4 When to Use Pointer Receivers
Use pointer receivers when:
- Struct is large
- You need to modify state
- Consistency across methods is needed
Rule of thumb:
If one method needs pointer receiver → make all methods pointer receivers.
5️⃣ Value vs Reference Semantics
This is critical.
In Go:
| Type | Behavior When Passed |
|---|---|
| int | Copied |
| string | Copied |
| array | Copied |
| struct | Copied |
| slice | Descriptor copied (shared backing array) |
| map | Reference |
| pointer | Reference |
Understanding this prevents subtle bugs.
6️⃣ The nil Pitfalls
nil is a valid zero value for:
- slices
- maps
- pointers
- interfaces
- channels
- functions
6.1 Nil Slice vs Empty Slice
var s []int
This is nil.
s == nil → true
len(s) → 0
But:
s := []int{}
This is not nil.
In most cases, prefer returning nil slices.
Why?
Because:
len(nilSlice)is safe- It behaves naturally
6.2 Nil Map Panic
var m map[string]int
m["x"] = 1 // panic
Always initialize maps.
6.3 Nil Pointer Panic
var u *User
u.Name = "Aditya" // panic
Always check before dereferencing.
6.4 Interface Nil Trap
This is advanced but important.
var u *User = nil
var i interface{} = u
Now:
i != nil
Even though underlying pointer is nil.
Because interface holds:
- Type
- Value
This confuses many developers.
7️⃣ Memory Model Basics (Conceptual)
Go has:
- Stack allocation
- Heap allocation
- Garbage collector
You don’t manually manage memory.
But:
If something “escapes” function scope → it goes to heap.
Example:
func createUser() *User {
u := User{Name: "A"}
return &u
}
This is safe.
Go performs escape analysis.
8️⃣ Common Beginner Mistakes
- Forgetting to initialize map
- Confusing slice copy vs reference
- Using value receiver unintentionally
- Dereferencing nil pointer
- Ignoring capacity behavior
- Assuming slice append modifies original always
9️⃣ Practical Refactoring Guidance
Take previous weeks’ code and:
- Replace fixed arrays with slices
- Replace struct value receivers with pointer receivers
- Replace global maps with initialized maps
- Add defensive nil checks
- Add tests for edge cases
This is how knowledge becomes practical.
1️⃣0️⃣ Summary
After this chapter, you should understand:
- Why slices are not arrays
- Why maps must be initialized
- When to use pointer receivers
- How value semantics differ from Python
- How nil behaves in Go
- How to avoid subtle memory bugs
This chapter builds your mental model of Go’s memory and data structures.
Without mastering this, concurrency (Week 5) will feel dangerous.
With this mastered, Go becomes predictable and powerful.