Chapter 4 – Solutions

Collections, Pointers & Memory


🟢 Level 1 – Fundamentals


1️⃣ Array Copy Behavior

Code

package main

import "fmt"

func modify(arr [3]int) {
	arr[0] = 100
}

func main() {
	a := [3]int{1, 2, 3}
	fmt.Println("Before:", a)
	modify(a)
	fmt.Println("After:", a)
}

Output

Before: [1 2 3]
After:  [1 2 3]

Why?

Arrays are value types.

When passed to a function, the entire array is copied.

You modified the copy — not the original.

Mental Model

Array = full value copy.


2️⃣ Slice Modification

Code

func modify(nums []int) {
	nums[0] = 100
}

Why does it change original?

Slices contain:

  • Pointer to array
  • Length
  • Capacity

When passed:

  • The slice descriptor is copied
  • But both descriptors point to the same underlying array

So modifying nums[0] modifies shared memory.

Mental Model

Slice = shared underlying array.


3️⃣ Slice Length & Capacity

nums := make([]int, 2, 5)

for i := 0; i < 5; i++ {
	nums = append(nums, i)
	fmt.Println("Len:", len(nums), "Cap:", cap(nums))
}

Observation

Capacity grows automatically when exceeded.

Go allocates a new array and copies values.

Mental Model

Append may reallocate memory.

Never assume append modifies original slice safely unless returned.


4️⃣ Nil Slice vs Empty Slice

var a []int
b := []int{}

fmt.Println(a == nil) // true
fmt.Println(b == nil) // false
fmt.Println(len(a))   // 0
fmt.Println(len(b))   // 0

Explanation

Both behave similarly in most operations.

Nil slice is usually preferred when returning from functions.


5️⃣ Safe Slice Copy

func clone(nums []int) []int {
	copySlice := make([]int, len(nums))
	copy(copySlice, nums)
	return copySlice
}

Why?

Without copy(), both slices share underlying array.


🟡 Level 2 – Maps & Semantics


6️⃣ Nil Map Panic

var m map[string]int
m["a"] = 1  // panic

Fix

m := make(map[string]int)

Why?

Nil map has no backing storage.

Reading is safe. Writing is not.


7️⃣ Safe Map Lookup

value, ok := m["key"]
if ok {
	fmt.Println("Found:", value)
} else {
	fmt.Println("Not found")
}

Why?

Map returns zero value if key doesn’t exist.

The ok prevents ambiguity.


8️⃣ Map Passed to Function

func update(m map[string]int) {
	m["x"] = 10
}

Original map changes.

Why?

Maps are reference types.


9️⃣ Slice Sharing Pitfall

a := []int{1, 2, 3, 4}
b := a[:2]
b[0] = 100

fmt.Println(a) // [100 2 3 4]

Proper copy

b := make([]int, 2)
copy(b, a[:2])

🔟 Remove Element from Slice

func removeAt(nums []int, index int) []int {
	if index < 0 || index >= len(nums) {
		return nums
	}
	return append(nums[:index], nums[index+1:]...)
}

Explanation

We:

  • Slice before index
  • Slice after index
  • Append together

🟠 Level 3 – Pointers & Struct Behavior


1️⃣1️⃣ Value vs Pointer Receiver

type Counter struct {
	Count int
}

func (c Counter) Increment() {
	c.Count++
}

func (c *Counter) IncrementPointer() {
	c.Count++
}

Why first doesn’t work?

Value receiver copies struct.

Pointer receiver modifies original.


1️⃣2️⃣ Pointer to Struct

func update(u *User) {
	u.Name = "Updated"
}

Must pass:

update(&user)

1️⃣3️⃣ Nil Pointer Panic

var u *User
if u != nil {
	fmt.Println(u.Name)
}

Always check before dereferencing.


1️⃣4️⃣ Method Consistency

If one method uses pointer receiver:

Make all methods pointer receivers.

Consistency avoids confusion.


1️⃣5️⃣ Large Struct Efficiency

Passing large structs by value copies memory.

Passing pointer avoids copying.

Use pointer receiver for large structs.


🔴 Level 4 – Advanced & Tricky


1️⃣6️⃣ Slice Append Trap

func add(nums []int) {
	nums = append(nums, 100)
}

Does not modify original because:

  • Append may reallocate new array
  • Slice descriptor inside function changes
  • Caller slice unchanged

Fix

func add(nums []int) []int {
	return append(nums, 100)
}

Call:

a = add(a)

1️⃣7️⃣ Map of Struct Trap

m["u1"].Name = "New"

Compiler error because map returns copy.

Fix

u := m["u1"]
u.Name = "New"
m["u1"] = u

OR

Use pointer in map:

map[string]*User

1️⃣8️⃣ Interface Nil Trap

var u *User = nil
var i interface{} = u

fmt.Println(i == nil) // false

Why?

Interface stores:

  • Type
  • Value

Type is *User, value is nil.

Therefore interface is not nil.


1️⃣9️⃣ Concurrent Map Access

Concurrent writes cause:

fatal error: concurrent map writes

Maps are not thread-safe.

Must use:

  • sync.Mutex
  • sync.Map
  • channels

(Week 5 topic)


2️⃣0️⃣ Escape Analysis

func createUser() *User {
	u := User{Name: "Test"}
	return &u
}

This is safe.

Go performs escape analysis.

Since pointer escapes function scope:

  • It allocates on heap
  • Not stack

Memory is safe.


🎯 Final Takeaways

After these solutions, she should understand:

  • Arrays are value types
  • Slices share underlying arrays
  • Maps must be initialized
  • Pointer receivers modify original
  • Nil behaves differently across types
  • Interface nil is tricky
  • Append must be returned
  • Map of structs returns copies
  • Concurrency + map = unsafe
  • Escape analysis protects memory

This chapter is now strong enough to:

  • Prepare the student for concurrency
  • Build debugging confidence
  • Eliminate beginner memory bugs


This site uses Just the Docs, a documentation theme for Jekyll.