Skip to content

Notes — Module 3: Functions

These are your personal study notes. Write freely and honestly. Incomplete notes are fine — they show where your understanding still needs work. Return to this file to add insights as they develop over time.

Module: [[go/3. Functions]] Topic: [[go]] Date started: YYYY-MM-DD Status: In progress


Concept Map

Sketch how the concepts in this module relate to each other. Fill in the Mermaid diagram.

mindmap
  root((Functions))
    Declarations and Signatures
      parameter shorthand (x, y int)
      multiple return types
      named return values
    Call-by-Value
      scalars copied independently
      slices share backing array
      maps share hash table
      pointers dereference to original
    Multiple Return Values
      (result, error) convention
      error always last
      _ to discard values
    Named Returns and Naked Returns
      documentation value
      defer interaction
      avoid in long functions
    error idiom
      errors.New
      fmt.Errorf with %w
      nil means success
    Variadic Functions
      ...T parameter is a slice
      spread with slice...
      only last param can be variadic
    First-Class Functions
      function types (func(T) T)
      nil zero value panics
      stored in variables and maps
    Closures
      capture by reference
      loop-variable pitfall
      Go 1.22 per-iteration fix
      stateful iterators
    Higher-Order Functions
      filter, map, reduce patterns
      function factories
      standard library examples
    Recursion
      base case required
      no tail-call optimization
      mutually recursive functions
    defer + Named Returns
      closure modifies named return
      error wrapping pattern
    Method Values
      bound receiver
      same as function value
      forward ref to Module 6

Alternative: draw this on paper, photo it, and link the image here.


Key Insights

The "aha moments" — the things that, once understood, made the rest clear. Be specific: "I finally understood X because Y" is more useful than "X makes sense".

  1. Closures capture references, not values: I finally understood why the loop-variable bug exists: the closure doesn't make a copy of i — it holds a reference to the variable itself. By the time the closures run, the loop is done and i is at its terminal value.
  2. Slices are three words, not one: The "photocopy" mental model clicked when I thought of a slice as a struct with three fields (pointer, len, cap). Copying a slice copies those three fields, not the underlying array. That's why element mutation is visible but append isn't.
  3. Add insights as you discover them

My Understanding

Explain the core concepts in your own words, as if teaching them to someone else. If you can't explain it simply, you don't understand it well enough yet.

Call-by-Value

Your explanation here

What I'm still unsure about: (e.g., when exactly does append allocate a new backing array vs reuse the existing one?)

Closures and Capture

Your explanation here

What I'm still unsure about: (e.g., how long does a captured variable live if the outer function has returned? Does the GC collect it?)

The (result, error) Convention

Your explanation here

What I'm still unsure about: (e.g., is there a way to make the compiler enforce that callers check the error?)

defer + Named Returns

Your explanation here

What I'm still unsure about: (e.g., what order do deferred closures run in if there are multiple in the same function?)


Connections to Other Topics

How does this module connect to things you already know?

This module's concept Connects to How
defer + named returns [[go/2. Control Flow]] defer was introduced in Module 2; named returns reveal the full power of defer by allowing deferred closures to modify what the function returns
(result, error) return [[go/8. Error Handling]] The convention learned here (error last, nil for success, fmt.Errorf with %w) is the foundation of all error handling in Go
Closures in goroutines [[go/9. Concurrency]] Goroutines are almost always launched as closures; the loop-variable capture pitfall is especially dangerous in concurrent code where closures run in separate goroutines

Questions That Arose

Log questions as they appear. Don't stop to answer them now — just capture them. Then move the serious ones to QUESTIONS.md.

  • What happens to captured variables when the outer function returns? Are they moved to the heap? → added to QUESTIONS.md as Q001
  • Can a deferred function itself defer another function? → might be answered experimentally
  • What is the performance cost of closures vs plain function calls? → might be answered in the profiling module

Code Snippets Worth Remembering

Patterns, idioms, or examples that captured something important.

The error wrapping pattern with defer + named returns

func operation(arg T) (result R, err error) {
    defer func() {
        if err != nil {
            err = fmt.Errorf("operation: %w", err)
        }
    }()
    // any error returned here is automatically wrapped
    return
}

Why I'm saving this: This is the cleanest way to ensure every error from a function carries the function's name as context. Used in production Go code to build rich error chains.


The function factory pattern

func multiplier(factor int) func(int) int {
    return func(n int) int { return n * factor }
}

double := multiplier(2)
triple := multiplier(3)

Why I'm saving this: Factory functions that return closures are Go's lightweight substitute for objects when you only need one operation. The captured variable (factor) is the "field", the returned closure is the "method".


Spreading a slice into a variadic call

nums := []int{1, 2, 3}
result := sum(nums...)  // equivalent to sum(1, 2, 3)

Why I'm saving this: Easy to forget the ... suffix at the call site when passing a slice to a variadic function. Without it, you'd get a compile error (wrong type).


What Tripped Me Up

Mistakes I made, misconceptions I had, things that confused me more than they should have. Being honest here helps you later.

  • Loop-variable capture — I initially thought each closure captured a copy of the loop variable at the time of creation. The 4, 4, 4, 4 output was a surprise. Now I remember: closures capture references.
  • append and call-by-value — I expected append(s, val) inside a function to grow the caller's slice. It doesn't — the caller's slice header is unchanged. I need to either return the new slice or pass *[]int.

Summary in My Own Words

Write a 3–5 sentence summary of this entire module without looking at any notes. If you can't do this, you need more study time.

Write your summary here after completing the module.


Last updated: YYYY-MM-DD