Go Pointers Basics — Tasks¶
Instructions¶
Each task has a description, starter code, expected output, and an evaluation checklist. Use pointers for mutation; values for read-only.
Task 1 — Increment Through Pointer¶
Difficulty: Beginner Topic: *T, &, *
Description: Implement incr(p *int) that increments *p.
Starter Code:
package main
import "fmt"
func incr(p *int) {
// TODO
}
func main() {
n := 5
incr(&n)
incr(&n)
fmt.Println(n) // 7
}
Expected Output:
Evaluation Checklist: - [ ] Takes *int - [ ] Dereferences with *p++ - [ ] Caller mutates via &n
Task 2 — Nil Check¶
Difficulty: Beginner
Description: Implement safeDeref(p *int) int returning *p or 0 if nil.
func safeDeref(p *int) int {
// TODO
return 0
}
func main() {
fmt.Println(safeDeref(nil)) // 0
n := 42
fmt.Println(safeDeref(&n)) // 42
}
Evaluation Checklist: - [ ] Returns 0 for nil - [ ] Returns *p otherwise - [ ] No panic
Task 3 — Constructor Pattern¶
Difficulty: Beginner
Description: Implement NewPoint(x, y int) *Point that allocates a Point.
type Point struct{ X, Y int }
func NewPoint(x, y int) *Point {
// TODO
return nil
}
func main() {
p := NewPoint(3, 4)
fmt.Println(p) // &{3 4}
}
Evaluation Checklist: - [ ] Returns *Point - [ ] Allocates with &Point{...} - [ ] Fields set correctly
Task 4 — Mutate Struct Field¶
Difficulty: Intermediate
Description: Implement (*User) SetName(name string) to update the user.
type User struct{ Name string; Age int }
// TODO: SetName method
func main() {
u := &User{Name: "old", Age: 30}
u.SetName("Ada")
fmt.Println(u.Name) // Ada
}
Evaluation Checklist: - [ ] Pointer receiver - [ ] Sets u.Name - [ ] Caller's u updated
Task 5 — Optional Pointer Field¶
Difficulty: Intermediate
Description: A Settings struct with *int Threshold (nil = no threshold). Implement Apply(value int) bool returning true if value passes (value >= threshold or threshold is nil).
type Settings struct{ Threshold *int }
func (s *Settings) Apply(value int) bool {
// TODO
return false
}
func main() {
fmt.Println((&Settings{}).Apply(5)) // true
t := 10
fmt.Println((&Settings{Threshold: &t}).Apply(5)) // false
fmt.Println((&Settings{Threshold: &t}).Apply(15)) // true
}
Evaluation Checklist: - [ ] Nil Threshold means no constraint - [ ] Non-nil compares value >= *Threshold
Task 6 — Linked List¶
Difficulty: Intermediate
Description: Build a singly linked list with Push(v int) and String() string.
type Node struct { Value int; Next *Node }
type List struct { Head *Node }
func (l *List) Push(v int) {
// TODO: prepend
}
func (l *List) String() string {
// TODO
return ""
}
func main() {
l := &List{}
l.Push(1); l.Push(2); l.Push(3)
fmt.Println(l) // [3 2 1]
}
Evaluation Checklist: - [ ] Push prepends - [ ] String walks the list - [ ] Empty list prints []
Task 7 — Generic ptrTo Helper¶
Difficulty: Intermediate
Description: Implement ptrTo[T any](v T) *T. Use it for optionals.
func ptrTo[T any](v T) *T {
// TODO
return nil
}
func main() {
p := ptrTo(42)
s := ptrTo("hi")
fmt.Println(*p, *s)
}
Evaluation Checklist: - [ ] Generic - [ ] Returns &v - [ ] Works with any type
Task 8 — Swap Two Variables¶
Difficulty: Intermediate
Description: Implement swap(a, b *int) that exchanges their values.
Evaluation Checklist: - [ ] Uses a and b - [ ] Tuple swap or temp variable - [ ] Both caller variables updated
Task 9 — Atomic Pointer¶
Difficulty: Advanced
Description: Use atomic.Pointer[Config] to share immutable configs between goroutines.
import "sync/atomic"
type Config struct { Verbose bool }
var configPtr atomic.Pointer[Config]
func main() {
configPtr.Store(&Config{Verbose: false})
go func() {
configPtr.Store(&Config{Verbose: true})
}()
// Read snapshot at any time:
cfg := configPtr.Load()
fmt.Println(cfg.Verbose)
}
Evaluation Checklist: - [ ] Uses atomic.Pointer[Config] - [ ] Store and Load methods - [ ] No data race even with concurrent writers
Task 10 — Pool Reusable Buffers¶
Difficulty: Advanced
Description: Use sync.Pool to reuse *Buffer instances.
import "sync"
type Buffer struct { Data [1024]byte }
func (b *Buffer) Reset() { /* clear */ }
var pool = sync.Pool{
New: func() any { return new(Buffer) },
}
func use() {
b := pool.Get().(*Buffer)
defer func() {
b.Reset()
pool.Put(b)
}()
// ... use b ...
}
func main() {
for i := 0; i < 100; i++ { use() }
fmt.Println("done")
}
Evaluation Checklist: - [ ] Pool.Get returns *Buffer - [ ] Reset before Put - [ ] Repeated use doesn't leak