Exercises — Module 3: Functions¶
Work through exercises in order — they're designed to build on each other. Attempt each problem genuinely before looking at the solution. Seeing the solution first might feel like progress, but it isn't.
Instructions¶
- Attempt first. Spend at least the estimated time on each problem before checking hints or solutions.
- Write your work. Don't just run code mentally — actually write or type your attempt.
- Check your answer against the acceptance criteria, not just the solution code.
- Score yourself honestly in the Scoring Log at the bottom.
- If you're stuck after a genuine effort, use the hints one at a time — not all at once.
Difficulty Legend¶
| Symbol | Difficulty | Expected Time | Points |
|---|---|---|---|
| 🟢 Easy | Recall and basic application | 5–10 min | 1 pt |
| 🟡 Medium | Requires combining 2+ concepts | 15–25 min | 2 pts |
| 🔴 Hard | Multi-step, requires real problem-solving | 30–60 min | 3 pts |
| ⭐ Expert | Open-ended; more than one good answer | 60+ min | 5 pts |
Exercise 1: Basic Function Declaration [🟢 Easy] [1 pt]¶
Context¶
The most fundamental skill in this module is writing a function with the correct signature and making it handle multiple cases correctly. clamp is a common utility function — it constrains a value to a range. This exercise ensures you can write a basic three-way decision in a function.
Task¶
Write a Go function clamp(val, min, max int) int that returns val constrained to the closed interval [min, max]:
- If val < min, return min
- If val > max, return max
- Otherwise return val
Call the function with at least 5 values that exercise all three branches and print the results.
Requirements¶
- Function signature is exactly
clamp(val, min, max int) int - Returns the correct value for
valbelow range, above range, and within range - At least 5 test calls covering all three branches
- Program compiles and runs without errors
Hints¶
Hint 1 (try without this first)
Use an `if`/`else if`/`else` chain in the function body. Check `val < min` first, then `val > max`, then fall through to the default case. A `switch` also works here.Expected Output / Acceptance Criteria¶
For inputs like -5, 0, 5, 10, 15 with range [0, 10]:
clamp(-5, 0, 10) = 0
clamp(0, 0, 10) = 0
clamp(5, 0, 10) = 5
clamp(10, 0, 10) = 10
clamp(15, 0, 10) = 10
Solution¶
Show Solution (attempt first!)
package main
import "fmt"
// clamp constrains val to the closed interval [min, max].
func clamp(val, min, max int) int {
if val < min {
return min
}
if val > max {
return max
}
return val
}
func main() {
testCases := [][3]int{
{-5, 0, 10},
{0, 0, 10},
{5, 0, 10},
{10, 0, 10},
{15, 0, 10},
}
for _, tc := range testCases {
result := clamp(tc[0], tc[1], tc[2])
fmt.Printf("clamp(%3d, %d, %d) = %d\n", tc[0], tc[1], tc[2], result)
}
}
Exercise 2: Call-by-Value Prediction [🟢 Easy] [1 pt]¶
Context¶
Understanding precisely what call-by-value means for slices and maps is one of the trickiest parts of Go for beginners. This exercise asks you to predict output before running code — the mental exercise of tracing through the call stack is more valuable than just writing code.
Task¶
For each of the following three code snippets, predict the output without running them. Write down your prediction, then verify by running the code. For any snippet where your prediction was wrong, write a one-sentence explanation of why.
Snippet A:
func setFirst(s []int, val int) {
s[0] = val
}
nums := []int{10, 20, 30}
setFirst(nums, 99)
fmt.Println(nums)
Snippet B:
func replaceSlice(s []int) {
s = []int{1, 2, 3}
}
nums := []int{10, 20, 30}
replaceSlice(nums)
fmt.Println(nums)
Snippet C:
func addKey(m map[string]int) {
m["new"] = 42
}
scores := map[string]int{"alice": 90}
addKey(scores)
fmt.Println(len(scores))
Requirements¶
- Predict the output of all three snippets before running them
- Run all three to verify
- For any wrong predictions, write an explanation
Hints¶
Hint 1 (conceptual hint)
Remember: call-by-value copies the argument. For slices, the copy shares the backing array with the original — so `s[0] = val` modifies the shared array and the caller sees the change. But `s = []int{1, 2, 3}` replaces the local copy's pointer and length — the caller's slice header is unchanged. For maps, the "value" being copied is a pointer to the hash table. Both the caller and the function point to the same table, so insertions in the function are visible to the caller.Expected Output / Acceptance Criteria¶
Snippet A: [99 20 30] — mutating through the shared backing array
Snippet B: [10 20 30] — replacing the local slice header does not affect the caller
Snippet C: 2 — the map has 2 entries after the function adds one
Solution¶
Show Solution
**Snippet A output:** `[99 20 30]` **Snippet B output:** `[10 20 30]` **Snippet C output:** `2` **Explanation:** Snippet A: `s[0] = val` assigns through the shared backing array. The slice value passed to `setFirst` is a three-word struct (pointer, length, capacity). The pointer still points to the same backing array as `nums`. Modifying `s[0]` modifies the first element of that shared array, which the caller sees. Snippet B: `s = []int{1, 2, 3}` replaces `s` — the local copy of the slice header — with a new slice header pointing to a brand-new backing array. The caller's `nums` variable still holds its original slice header (pointing to the original array with values `[10, 20, 30]`). The reassignment affects only the local copy. Snippet C: The map variable `m` received by `addKey` is a copy of the map header — but the map header is essentially a pointer to the hash table. Both `scores` in `main` and `m` in `addKey` point to the same hash table. When `addKey` inserts `"new"`, it modifies that shared table. The caller sees `len(scores) == 2`. **The rule to remember:** Mutations through a shared pointer (element assignment in a slice, insertion in a map) are visible to the caller. Replacing the pointer itself (reassigning a slice to a new slice, reassigning a map variable) is not visible to the caller.Exercise 3: Variadic Sum and Statistics [🟢 Easy] [1 pt]¶
Context¶
Variadic functions are common in Go — fmt.Println, fmt.Printf, append, and many utility functions use them. This exercise builds the basic mechanics: writing a variadic function, using the parameter as a slice inside the function, and using the ... spread operator at the call site.
Task¶
Write two Go functions:
1. sum(nums ...int) int — returns the sum of all arguments; returns 0 for no arguments
2. mean(nums ...int) float64 — returns the arithmetic mean of all arguments; returns 0.0 for no arguments
Call each function:
- With no arguments
- With individual arguments: sum(1, 2, 3, 4, 5)
- With a spread slice: slice := []int{10, 20, 30}; sum(slice...)
Requirements¶
- Both functions use variadic parameters (
...int) - The
...spread operator is used at least once at a call site - Both functions handle the empty case (no arguments) without panicking
-
meanreturnsfloat64with correct decimal precision (not integer division)
Hints¶
Hint 1
Inside a variadic function, the parameter behaves as a slice. Check `len(nums) == 0` to handle the empty case. For `mean`, convert to `float64` before dividing: `float64(sum) / float64(len(nums))`.Hint 2
To spread a slice into a variadic call: `sum(slice...)`. The type of `slice` must match the variadic parameter type (`[]int` for `...int`).Expected Output / Acceptance Criteria¶
sum() = 0
sum(1,2,3,4,5) = 15
sum(slice...) = 60
mean() = 0.00
mean(1,2,3) = 2.00
mean(10,20,30) = 20.00
Solution¶
Show Solution
package main
import "fmt"
// sum returns the sum of all its arguments, or 0 if called with no arguments.
func sum(nums ...int) int {
total := 0
for _, n := range nums {
total += n
}
return total
}
// mean returns the arithmetic mean of its arguments, or 0.0 if called with none.
func mean(nums ...int) float64 {
if len(nums) == 0 {
return 0.0
}
return float64(sum(nums...)) / float64(len(nums))
}
func main() {
slice := []int{10, 20, 30}
fmt.Printf("sum() = %d\n", sum())
fmt.Printf("sum(1,2,3,4,5) = %d\n", sum(1, 2, 3, 4, 5))
fmt.Printf("sum(slice...) = %d\n", sum(slice...))
fmt.Printf("mean() = %.2f\n", mean())
fmt.Printf("mean(1,2,3) = %.2f\n", mean(1, 2, 3))
fmt.Printf("mean(10,20,30) = %.2f\n", mean(10, 20, 30))
}
Exercise 4: Higher-Order Filter and Map [🟡 Medium] [2 pts]¶
Context¶
Higher-order functions are one of Go's most powerful compositional tools. The standard library's sort.Slice, http.HandleFunc, and testing APIs all rely on them. This exercise builds the pattern from scratch: writing generic (for []int) filter and map functions, then composing them.
Task¶
Write three functions:
1. filter(nums []int, predicate func(int) bool) []int — returns elements for which predicate returns true
2. transform(nums []int, f func(int) int) []int — returns a new slice with f applied to each element
3. In main, use these functions with inline anonymous functions to: (a) filter a list of integers to keep only those divisible by 3, then (b) double each remaining element, and print the result.
Starting list: []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}
Requirements¶
-
filterandtransformhave the exact signatures above - Inline anonymous functions are used as arguments (not named functions)
- The two operations are chained: transform is called on the output of filter
- The final result is printed
Hints¶
Hint 1 (structural hint)
`transform` follows the same pattern: iterate, apply `f`, collect into result.Hint 2 (chaining hint)
To chain the two operations: `transform(filter(nums, ...), ...)`. The inner call runs first and produces the filtered slice, which is then passed directly to `transform`.Expected Output / Acceptance Criteria¶
Solution¶
Show Solution
package main
import "fmt"
// filter returns elements of nums for which predicate returns true.
func filter(nums []int, predicate func(int) bool) []int {
var result []int
for _, n := range nums {
if predicate(n) {
result = append(result, n)
}
}
return result
}
// transform returns a new slice with f applied to each element.
func transform(nums []int, f func(int) int) []int {
result := make([]int, len(nums))
for i, n := range nums {
result[i] = f(n)
}
return result
}
func main() {
nums := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}
divisibleBy3 := filter(nums, func(n int) bool {
return n%3 == 0
})
fmt.Println("Filtered (div by 3):", divisibleBy3)
doubled := transform(divisibleBy3, func(n int) int {
return n * 2
})
fmt.Println("Doubled: ", doubled)
}
Exercise 5: Closure Counter with Reset [🟡 Medium] [2 pts]¶
Context¶
Closures with captured mutable state are one of Go's most versatile patterns. They allow you to create stateful objects without defining a struct or a method. This exercise builds a counter factory that returns two closures — increment and reset — that share the same captured state.
Task¶
Write a function newCounter() (increment func(), reset func(), value func() int) that returns three closures sharing the same internal count:
- increment() — increases the count by 1
- reset() — sets the count back to 0
- value() — returns the current count without modifying it
In main, create two independent counters and demonstrate that they are independent.
Requirements¶
- All three returned functions close over the same
countvariable -
increment,reset, andvaluehave the signatures shown (no parameters) - Two counters created by separate calls to
newCounterare independent - Demonstrate independence by incrementing one counter while leaving the other at its current value
Hints¶
Hint 1 (structural hint)
Three closures, all referencing the same `count` variable declared in `newCounter`'s scope.Expected Output / Acceptance Criteria¶
(Exact values may vary based on your increment/reset sequence, but independence must be demonstrated.)Solution¶
Show Solution
package main
import "fmt"
// newCounter returns three functions (increment, reset, value) that share
// the same internal count variable via closure capture.
func newCounter() (func(), func(), func() int) {
count := 0
increment := func() { count++ }
reset := func() { count = 0 }
value := func() int { return count }
return increment, reset, value
}
func main() {
// Create counter A
incA, resetA, valueA := newCounter()
fmt.Printf("Counter A: %d\n", valueA()) // 0
incA()
incA()
incA()
fmt.Printf("Counter A: %d\n", valueA()) // 3
// Create counter B — independent of A
incB, _, valueB := newCounter()
incB()
incB()
fmt.Printf("Counter B (independent): %d\n", valueB()) // 2
// Resetting A does not affect B
resetA()
fmt.Printf("Counter A after reset: %d\n", valueA()) // 0
fmt.Printf("Counter B still: %d\n", valueB()) // 2
}
Exercise 6: The Loop-Variable Capture Pitfall [🟡 Medium] [2 pts]¶
Context¶
The loop-variable capture bug is one of the most common sources of subtle bugs in Go, especially in concurrent code. Before Go 1.22, all iterations of a for loop shared one loop variable. This exercise demonstrates the bug, then asks you to fix it using the pre-1.22 technique (shadowing), which is important to recognize in existing codebases.
Task¶
Part A: Write the following code exactly and predict what it prints when run with go 1.21 semantics (or without a go.mod, which defaults to old behavior):
funcs := make([]func(), 4)
for i := 0; i < 4; i++ {
funcs[i] = func() { fmt.Println(i) }
}
for _, f := range funcs {
f()
}
Part B: Fix the code using the variable shadowing technique (introduce i := i inside the loop body) so it prints 0, 1, 2, 3.
Part C: Show the same fix using an immediately invoked function (IIFE) that takes i as a parameter.
Requirements¶
- Part A: Explain (in a comment or print statement) what the buggy version prints and why
- Part B: Correct output using variable shadowing
- Part C: Correct output using IIFE
- Both B and C print
0\n1\n2\n3\n
Hints¶
Hint 1 (pre-1.22 behavior)
In Go < 1.22, the `for i := 0; i < 4; i++` loop creates one `i` variable that is shared across all iterations. All four closures capture a reference to the same variable. By the time any closure runs (after the loop completes), `i` is 4 (the value that caused the loop condition to fail).Hint 2 (shadowing fix)
Inside the loop body, before creating the closure, add: `i := i`. This declares a new variable named `i` in the current block scope, initialized to the current loop iteration's value. The closure captures this block-scoped `i`, not the loop variable.Hint 3 (IIFE fix)
Wrap the closure creation in an immediately invoked function that takes `i` as a parameter: The outer `i` (the parameter) is a new variable that receives a copy of the loop `i` at call time.Expected Output / Acceptance Criteria¶
Part A output (pre-1.22): 4\n4\n4\n4\n (or the loop's post-condition value, which is 4)
Parts B and C output: 0\n1\n2\n3\n
Solution¶
Show Solution
package main
import "fmt"
func main() {
// Part A: The bug (pre-Go 1.22 behavior)
// All closures capture the same 'i' variable. When they run,
// i == 4 (the value that terminated the loop).
fmt.Println("Part A (buggy):")
funcs := make([]func(), 4)
for i := 0; i < 4; i++ {
funcs[i] = func() { fmt.Println(i) } // captures the loop variable i
}
for _, f := range funcs {
f() // In Go < 1.22: prints 4, 4, 4, 4
// In Go 1.22+: prints 0, 1, 2, 3 (each iter gets its own i)
}
// Part B: Fix via variable shadowing
fmt.Println("\nPart B (shadow fix):")
funcsB := make([]func(), 4)
for i := 0; i < 4; i++ {
i := i // shadow: new per-iteration variable, initialized to current i
funcsB[i] = func() { fmt.Println(i) }
}
for _, f := range funcsB {
f() // prints 0, 1, 2, 3
}
// Part C: Fix via immediately invoked function expression (IIFE)
fmt.Println("\nPart C (IIFE fix):")
funcsC := make([]func(), 4)
for i := 0; i < 4; i++ {
funcsC[i] = func(captured int) func() {
return func() { fmt.Println(captured) }
}(i) // i is passed by value to the IIFE — a new 'captured' variable per call
}
for _, f := range funcsC {
f() // prints 0, 1, 2, 3
}
}
Exercise 7: defer + Named Returns for Error Wrapping [🔴 Hard] [3 pts]¶
Context¶
The deferred-closure-modifies-named-return pattern is one of Go's most powerful and subtle idioms. It enables guaranteed error wrapping without repeating fmt.Errorf("funcName: %w", err) on every error return path. This exercise builds the pattern step by step.
Task¶
Write a function openAndRead(path string) (content string, err error) that:
1. Uses named return values content and err
2. Has a defer at the top that wraps any non-nil error with fmt.Errorf("openAndRead %q: %w", path, err)
3. Opens the file with os.Open, returns immediately on error (the defer will wrap it)
4. Reads the file with io.ReadAll, returns immediately on error (the defer will wrap it)
5. Sets content = string(data) and returns
Call it with /etc/hostname (should succeed on Linux) and with /nonexistent/file (should fail), and print both outcomes.
Verify using errors.Is that the wrapped error preserves the original os.PathError for the failure case.
Requirements¶
- Named return values
content string, err errorin the signature - A single
defer func() { if err != nil { ... } }()at the top of the function body - The deferred closure wraps err using
fmt.Errorf("openAndRead %q: %w", path, err) - The function body does NOT manually wrap errors — just
returnon error -
errors.Is(wrappedErr, fs.ErrNotExist)returns true for the missing file case (demonstrating unwrapping)
Hints¶
Hint 1 (structure)
The `defer` captures `err` by reference. Whenever the function body sets `err` and returns, the deferred closure runs and wraps it.Hint 2 (errors package)
To check if an error is "file not found": `errors.Is(err, fs.ErrNotExist)`. Import `"io/fs"`. The `%w` verb in `fmt.Errorf` preserves the error chain, so `errors.Is` can unwrap through multiple layers to find `fs.ErrNotExist`.Hint 3 (imports)
You need: `"errors"`, `"fmt"`, `"io"`, `"io/fs"`, `"os"`Expected Output / Acceptance Criteria¶
/etc/hostname: <hostname content> (varies by system)
/nonexistent/file: error: openAndRead "/nonexistent/file": open /nonexistent/file: no such file or directory
Is ErrNotExist? true
Solution¶
Show Solution
package main
import (
"errors"
"fmt"
"io"
"io/fs"
"os"
)
// openAndRead reads the entire content of a file as a string.
// Any error is wrapped with the function name and path for context.
func openAndRead(path string) (content string, err error) {
// Deferred closure captures 'err' by reference.
// It runs just before openAndRead returns, after the function body sets err.
defer func() {
if err != nil {
err = fmt.Errorf("openAndRead %q: %w", path, err)
}
}()
f, err := os.Open(path)
if err != nil {
return // naked return; deferred closure will wrap err
}
defer f.Close()
data, err := io.ReadAll(f)
if err != nil {
return // naked return; deferred closure will wrap err
}
content = string(data)
return // success: err is nil, deferred closure does nothing
}
func main() {
// Success case
if content, err := openAndRead("/etc/hostname"); err != nil {
fmt.Println("unexpected error:", err)
} else {
fmt.Printf("/etc/hostname: %q\n", content)
}
// Failure case
_, err := openAndRead("/nonexistent/file")
if err != nil {
fmt.Printf("/nonexistent/file: error: %v\n", err)
fmt.Printf("Is ErrNotExist? %v\n", errors.Is(err, fs.ErrNotExist))
}
}
Exercise 8: Recursive Tree Traversal [🔴 Hard] [3 pts]¶
Context¶
Recursion is natural for tree-shaped data structures. This exercise builds a simple binary tree and uses mutual recursion (depth calculation calls a helper) alongside a recursive traversal that collects values in order.
Task¶
Define a binary tree node type:
Write two recursive functions:
1. insert(root *TreeNode, val int) *TreeNode — inserts val into a binary search tree (BST), returning the root. If root is nil, return a new node. If val < root.Value, recurse left; if val > root.Value, recurse right; if equal, don't insert.
2. inorder(root *TreeNode) []int — returns the values of the tree in ascending order (in-order traversal: left, root, right).
Build a BST by inserting 5, 3, 7, 1, 4, 6, 8 and print the in-order result.
Requirements¶
-
insertis recursive and returns the (possibly new) root -
inorderis recursive and returns a[]intin sorted order - The in-order output of inserting
5, 3, 7, 1, 4, 6, 8is[1 3 4 5 6 7 8] - Both functions handle
nilroot correctly (no nil pointer dereference)
Hints¶
Hint 1 (insert pattern)
Hint 2 (inorder pattern)
In-order traversal: left subtree, then current node, then right subtree. Use `append` to combine the results:Expected Output / Acceptance Criteria¶
Solution¶
Show Solution
package main
import "fmt"
// TreeNode is a node in a binary search tree.
type TreeNode struct {
Value int
Left *TreeNode
Right *TreeNode
}
// insert adds val to the BST rooted at root and returns the (possibly new) root.
// Duplicate values are ignored.
func insert(root *TreeNode, val int) *TreeNode {
if root == nil {
return &TreeNode{Value: val}
}
switch {
case val < root.Value:
root.Left = insert(root.Left, val) // recurse into left subtree
case val > root.Value:
root.Right = insert(root.Right, val) // recurse into right subtree
// equal: do nothing — no duplicates
}
return root
}
// inorder returns the values of the BST in ascending order.
func inorder(root *TreeNode) []int {
if root == nil {
return nil
}
var result []int
result = append(result, inorder(root.Left)...) // left subtree
result = append(result, root.Value) // current node
result = append(result, inorder(root.Right)...) // right subtree
return result
}
func main() {
values := []int{5, 3, 7, 1, 4, 6, 8}
var root *TreeNode
for _, v := range values {
root = insert(root, v)
}
fmt.Println("In-order traversal:", inorder(root))
}
Exercise 9: Middleware Chain [⭐ Expert] [5 pts]¶
Context¶
Middleware chains are a foundational pattern in Go web servers and many other systems. A middleware wraps a handler: it does some work before calling the next handler, and optionally some work after. Chaining middlewares composes them into a pipeline. This exercise builds a simplified (non-HTTP) version of this pattern using function types.
Task¶
Define a Handler type as func(input string) string. Write a Middleware type as func(Handler) Handler.
Implement three middlewares:
1. logger(name string) Middleware — logs the function name, input, and output to stdout before returning the result
2. trimSpace() Middleware — trims leading/trailing whitespace from the input before passing it to the next handler
3. addPrefix(prefix string) Middleware — prepends prefix to the output of the next handler
Write a chain(middlewares ...Middleware) Middleware function that composes middlewares right-to-left (the first middleware in the list is the outermost — applied last to the handler, called first when the chain runs).
Write a simple base handler func(s string) string { return "hello, " + s }.
Apply all three middlewares to the base handler using chain, call the result with " world ", and show the output.
Requirements¶
-
HandlerandMiddlewaretypes are defined as specified - All three middleware factories work correctly
-
chaincomposes any number of middlewares - The final call with
" world "trims spaces, logs, prepends prefix, and produces a sensible output - Explain in a comment what order the middlewares apply
Hints¶
Hint 1 (structural hint)
A middleware takes a handler and returns a new handler that wraps it:Hint 2 (chain composition)
`chain` should apply middlewares in reverse order so the first middleware in the list is outermost:Hint 3 (application)
To apply the chained middleware to a base handler:Expected Output / Acceptance Criteria¶
(Exact format depends on your logger and prefix middleware implementations.)Solution¶
Show Solution
package main
import (
"fmt"
"strings"
)
// Handler processes a string input and returns a string output.
type Handler func(input string) string
// Middleware wraps a Handler, returning a new Handler.
type Middleware func(Handler) Handler
// logger is a middleware factory that logs the handler's name, input, and output.
func logger(name string) Middleware {
return func(next Handler) Handler {
return func(input string) string {
result := next(input)
fmt.Printf("[%s] input=%q output=%q\n", name, input, result)
return result
}
}
}
// trimSpace is a middleware that trims whitespace from the input.
func trimSpace() Middleware {
return func(next Handler) Handler {
return func(input string) string {
return next(strings.TrimSpace(input))
}
}
}
// addPrefix is a middleware factory that prepends prefix to the handler's output.
func addPrefix(prefix string) Middleware {
return func(next Handler) Handler {
return func(input string) string {
return prefix + next(input)
}
}
}
// chain composes middlewares: the first middleware listed is the outermost
// (runs first when the chain is called, wraps the rest).
func chain(mws ...Middleware) Middleware {
return func(next Handler) Handler {
// Apply in reverse so mws[0] wraps everything else
for i := len(mws) - 1; i >= 0; i-- {
next = mws[i](next)
}
return next
}
}
func main() {
// Base handler: greets its input
base := Handler(func(s string) string {
return "hello, " + s
})
// Chain: logger is outermost (runs first), then trimSpace, then addPrefix.
// Execution order when called:
// 1. logger receives " world ", calls next (trimSpace) with " world "
// 2. trimSpace trims to "world", calls next (addPrefix) with "world"
// 3. addPrefix calls base with "world", gets "hello, world"
// 4. addPrefix prepends "[RESULT] ", returns "[RESULT] hello, world"
// 5. trimSpace returns "[RESULT] hello, world" to logger
// 6. logger prints its log line, returns "[RESULT] hello, world"
wrapped := chain(
logger("main"),
trimSpace(),
addPrefix("[RESULT] "),
)(base)
result := wrapped(" world ")
fmt.Println(result)
}
Scoring Log¶
Record your performance honestly. Include the date and whether you used hints.
| Exercise | Date | Score | Used Hints? | Notes |
|---|---|---|---|---|
| Exercise 1 — Basic Function Declaration | — | —/1 | — | — |
| Exercise 2 — Call-by-Value Prediction | — | —/1 | — | — |
| Exercise 3 — Variadic Sum and Statistics | — | —/1 | — | — |
| Exercise 4 — Higher-Order Filter and Map | — | —/2 | — | — |
| Exercise 5 — Closure Counter with Reset | — | —/2 | — | — |
| Exercise 6 — Loop-Variable Capture Pitfall | — | —/2 | — | — |
| Exercise 7 — defer + Named Returns | — | —/3 | — | — |
| Exercise 8 — Recursive Tree Traversal | — | —/3 | — | — |
| Exercise 9 — Middleware Chain | — | —/5 | — | — |
| Total | —/20 |
Passing threshold: 13/20 (65%). Aim for 17/20 (85%) before taking the test.