Go Anonymous Functions — Tasks¶
Instructions¶
Each task includes a description, starter code, expected output, and an evaluation checklist. Use anonymous functions where natural; extract to named functions when bodies grow long.
Task 1 — Sort Comparator¶
Difficulty: Beginner Topic: Anonymous function as sort.Slice argument
Description: Sort a slice of Person{Name, Age} by Age ascending, using sort.Slice and an inline comparator.
Starter Code:
package main
import (
"fmt"
"sort"
)
type Person struct {
Name string
Age int
}
func main() {
people := []Person{{"Charlie", 30}, {"Alice", 25}, {"Bob", 22}}
// TODO: sort.Slice with anonymous comparator
fmt.Println(people)
}
Expected Output:
Evaluation Checklist: - [ ] Uses sort.Slice - [ ] Comparator is anonymous (inline) - [ ] Returns people[i].Age < people[j].Age
Task 2 — IIFE for Scoped Init¶
Difficulty: Beginner Topic: Immediately-Invoked Function Expression
Description: Compute the maximum of three integers using an IIFE that returns the result. Don't define a separate function.
Starter Code:
package main
import "fmt"
func main() {
a, b, c := 5, 12, 7
max := func(x, y, z int) int {
// TODO: compute max
return 0
}(a, b, c)
fmt.Println(max)
}
Expected Output:
Evaluation Checklist: - [ ] Uses an IIFE (function literal followed by ()) - [ ] Computes max correctly for any 3 ints - [ ] Result is bound to max outside
Task 3 — Defer With Recover¶
Difficulty: Beginner Topic: Anonymous function in defer
Description: Write safeDivide(a, b int) (int, error) that uses a/b inside the function; if a panic occurs, recover and return the panic message as an error.
Starter Code:
package main
import "fmt"
func safeDivide(a, b int) (result int, err error) {
defer func() {
// TODO: recover and set err
}()
return a / b, nil
}
func main() {
r, err := safeDivide(10, 2)
fmt.Println(r, err)
r, err = safeDivide(10, 0)
fmt.Println(r, err)
}
Expected Output:
Evaluation Checklist: - [ ] Uses defer func() {...}() with recover() - [ ] Sets err (named return) when panic occurs - [ ] Returns (a/b, nil) for valid input
Task 4 — Goroutine With Argument¶
Difficulty: Beginner Topic: Pass loop variable as goroutine argument
Description: Spawn 5 goroutines that each print their index. Use sync.WaitGroup and pass the index as an argument (don't rely on capture).
Starter Code:
package main
import (
"fmt"
"sync"
)
func main() {
var wg sync.WaitGroup
for i := 0; i < 5; i++ {
wg.Add(1)
// TODO: go func(...) { ... }(i)
}
wg.Wait()
}
Expected Output (any order):
Evaluation Checklist: - [ ] Uses go func(i int) { ... }(i) form - [ ] Calls wg.Done() (preferably with defer) - [ ] No race condition on i - [ ] wg.Wait() blocks until all done
Task 5 — Higher-Order Filter¶
Difficulty: Intermediate Topic: Anonymous predicate
Description: Implement filter[T any](xs []T, keep func(T) bool) []T. Use it to filter even numbers and short strings.
Starter Code:
package main
import "fmt"
func filter[T any](xs []T, keep func(T) bool) []T {
// TODO
return nil
}
func main() {
evens := filter([]int{1, 2, 3, 4, 5, 6}, func(n int) bool {
return n%2 == 0
})
fmt.Println(evens)
short := filter([]string{"a", "bbb", "cc", "ddd"}, func(s string) bool {
return len(s) <= 2
})
fmt.Println(short)
}
Expected Output:
Evaluation Checklist: - [ ] Generic over T - [ ] Pre-allocates output slice (efficient) - [ ] Uses anonymous predicate at call site - [ ] Works with int and string
Task 6 — Decorator Pattern¶
Difficulty: Intermediate Topic: Function literal returning a wrapped function
Description: Implement timed(label string, fn func()) func() that returns a function which, when called, runs fn and prints how long it took.
Starter Code:
package main
import (
"fmt"
"time"
)
func timed(label string, fn func()) func() {
// TODO: return a function that wraps fn with timing
return nil
}
func main() {
work := func() {
time.Sleep(50 * time.Millisecond)
}
timedWork := timed("work", work)
timedWork()
timedWork()
}
Expected Output (timings vary):
Evaluation Checklist: - [ ] timed returns a function - [ ] The returned function records start, calls fn, prints duration - [ ] Captures label and fn - [ ] Can be called multiple times
Task 7 — Functional Options¶
Difficulty: Intermediate Topic: Anonymous functions as configuration
Description: Implement a Server config with Addr, Port, Timeout. Provide WithX options as functions that mutate *Server. The constructor NewServer(opts ...func(*Server)) *Server applies them.
Starter Code:
package main
import (
"fmt"
"time"
)
type Server struct {
Addr string
Port int
Timeout time.Duration
}
func NewServer(opts ...func(*Server)) *Server {
s := &Server{Addr: "localhost", Port: 8080, Timeout: 30 * time.Second}
// TODO
return s
}
func WithAddr(a string) func(*Server) { return func(s *Server) { s.Addr = a } }
func WithPort(p int) func(*Server) { return func(s *Server) { s.Port = p } }
func WithTimeout(d time.Duration) func(*Server) { return func(s *Server) { s.Timeout = d } }
func main() {
s := NewServer(WithPort(9000), WithTimeout(5*time.Second))
fmt.Printf("%+v\n", s)
}
Expected Output:
Evaluation Checklist: - [ ] Each WithX returns a function value - [ ] Constructor applies opts in order - [ ] Defaults are applied first - [ ] Anonymous function used inside each WithX
Task 8 — Recursive Anonymous Function¶
Difficulty: Intermediate Topic: The recursion-by-name workaround
Description: Compute factorial using a recursive anonymous function (declared via var ... func).
Starter Code:
package main
import "fmt"
func main() {
var fact func(int) int
// TODO: assign fact to a recursive anonymous function
for i := 0; i <= 6; i++ {
fmt.Printf("%d! = %d\n", i, fact(i))
}
}
Expected Output:
Evaluation Checklist: - [ ] Uses var fact func(int) int then fact = func(...){...} - [ ] Base case: n <= 1 returns 1 - [ ] Recursive case: n * fact(n-1)
Task 9 — Pipeline of Functions¶
Difficulty: Advanced Topic: Slice of function values
Description: Implement pipeline(input string, steps ...func(string) string) string that threads input through each step. Define steps as anonymous functions in main.
Starter Code:
package main
import (
"fmt"
"strings"
)
func pipeline(input string, steps ...func(string) string) string {
// TODO
return input
}
func main() {
out := pipeline(" Hello World ",
strings.TrimSpace,
strings.ToLower,
func(s string) string { return strings.ReplaceAll(s, " ", "-") },
)
fmt.Println(out)
}
Expected Output:
Evaluation Checklist: - [ ] Variadic of func(string) string - [ ] Threads result of each step into next - [ ] Mixes named (strings.TrimSpace) and anonymous functions - [ ] Empty steps returns input unchanged
Task 10 — Memoization Helper¶
Difficulty: Advanced Topic: Closure with map state
Description: Implement memoize(fn func(int) int) func(int) int that caches results. Use it on a slow Fibonacci function and verify only one computation happens per unique input.
Starter Code:
package main
import "fmt"
var calls int
func slowFib(n int) int {
calls++
if n < 2 {
return n
}
return slowFib(n-1) + slowFib(n-2)
}
func memoize(fn func(int) int) func(int) int {
// TODO: closure with cache
return nil
}
func main() {
fast := memoize(slowFib)
for i := 0; i < 10; i++ {
_ = fast(i)
}
fmt.Println("call count:", calls)
// Without memoize:
calls = 0
for i := 0; i < 10; i++ {
_ = slowFib(i)
}
fmt.Println("non-memoized:", calls)
}
Expected Output (approximate):
(Memoization caches each top-level result; slowFib internally still recurses uncached. To memoize the recursion too, you'd need a mutually-recursive structure.)
Evaluation Checklist: - [ ] Captures a map[int]int cache - [ ] On cache hit, returns cached value - [ ] On miss, calls fn, caches result, returns - [ ] memoize returns a closure of correct signature
Bonus Task — IIFE for Conditional Expression¶
Difficulty: Advanced Topic: IIFE replacing missing ternary
Description: Go has no ternary cond ? a : b. Write an IIFE that returns "big" if n > 100 else "small". Compare with using a regular if/else block.
Starter Code:
package main
import "fmt"
func main() {
n := 42
// TODO: use IIFE to assign label
var label string
label = ""
fmt.Println(label)
}
Expected Output:
Evaluation Checklist: - [ ] IIFE returns "big" or "small" based on n - [ ] No separate named function - [ ] Result assigned to label - [ ] Discusses (in a comment) why a regular if/else is usually better
// IIFE version:
label := func() string {
if n > 100 {
return "big"
}
return "small"
}()
// Plain if/else version (preferred for readability):
var label string
if n > 100 {
label = "big"
} else {
label = "small"
}
The IIFE has the advantage of returning a value (assignable in one expression) but adds a layer of indirection. Use sparingly.