Go/
Lesson

AI generates anonymous functions and closures constantly in Go, for HTTPWhat is http?The protocol browsers and servers use to exchange web pages, API data, and other resources, defining how requests and responses are formatted. handlers, goroutineWhat is goroutine?A lightweight concurrent function in Go launched with the go keyword - much cheaper than an OS thread (~2KB vs ~1MB of stack). launches, sort comparators, and middlewareWhat is middleware?A function that runs between receiving a request and sending a response. It can check authentication, log data, or modify the request before your main code sees it. chains. You rarely need to write them from scratch, but you absolutely need to read them. The critical skill is spotting what variables a closureWhat is closure?A function that remembers variables from the surrounding code where it was created, even after that surrounding code has finished running. captures and whether that capture creates a bug.

Anonymous functions

A function without a name. AI uses them whenever a short function is needed once:

// Assigned to a variable
double := func(x int) int {
    return x * 2
}
fmt.Println(double(5)) // 10

// Passed directly as an argument
sort.Slice(users, func(i, j int) bool {
    return users[i].Name < users[j].Name
})

The syntax is identical to a named function minus the name. You can assign them to variables, pass them as arguments, or return them from other functions.

02

How closures capture variables

A closureWhat is closure?A function that remembers variables from the surrounding code where it was created, even after that surrounding code has finished running. is an anonymous functionWhat is anonymous function?A function with no name, typically assigned to a variable or passed as an argument - used frequently with arrow functions and callbacks. that references variables from its enclosing scopeWhat is scope?The area of your code where a variable is accessible; variables declared inside a function or block are invisible outside it.. The closure captures those variables by reference, not by value:

func makeCounter() func() int {
    count := 0
    return func() int {
        count++ // captures count by reference
        return count
    }
}

func main() {
    counter := makeCounter()
    fmt.Println(counter()) // 1
    fmt.Println(counter()) // 2
    fmt.Println(counter()) // 3

    // Independent instance, separate count
    other := makeCounter()
    fmt.Println(other())   // 1
}

Each call to makeCounter() creates a new count variable. The returned closure holds a reference to that specific count. This is how closures maintain private state.

TermDefinitionKey detail
Anonymous functionFunction with no nameCan be assigned, passed, returned
ClosureAnonymous function + captured variablesVariables are captured by reference
Captured variableVariable from outer scope used inside closureShared between closure and outer scope
IIFEImmediately invoked function expressionfunc() { ... }(), runs right away
03

Where AI uses closures in Go

These are the four patterns you'll see most in AI-generated Go:

HTTPWhat is http?The protocol browsers and servers use to exchange web pages, API data, and other resources, defining how requests and responses are formatted. handlers with injected dependencies

func makeUserHandler(db *sql.DB) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        // closure captures db
        rows, err := db.Query("SELECT * FROM users")
        if err != nil {
            http.Error(w, err.Error(), 500)
            return
        }
        defer rows.Close()
        // ...
    }
}

The closureWhat is closure?A function that remembers variables from the surrounding code where it was created, even after that surrounding code has finished running. captures db so the handler has access to the database without global variables.

GoroutineWhat is goroutine?A lightweight concurrent function in Go launched with the go keyword - much cheaper than an OS thread (~2KB vs ~1MB of stack). launches

for _, url := range urls {
    go func(u string) {
        resp, err := http.Get(u)
        // ... process resp
    }(url) // pass url as argument
}

Sort comparators

sort.Slice(products, func(i, j int) bool {
    return products[i].Price < products[j].Price
})

MiddlewareWhat is middleware?A function that runs between receiving a request and sending a response. It can check authentication, log data, or modify the request before your main code sees it. chains

func withAuth(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        token := r.Header.Get("Authorization")
        if token == "" {
            http.Error(w, "unauthorized", 401)
            return
        }
        next.ServeHTTP(w, r)
    })
}
AI pitfall
AI sometimes generates closures that capture large structs or slices by reference when a copy would be safer. If a closure is passed to a goroutine that runs concurrently, and the outer scope modifies the captured variable, you get a data race. When reviewing AI-generated closures in concurrent code, check whether captured variables are also modified elsewhere.
04

The loop variable capture bug

This is the most famous Go gotcha, and AI still generates it. In Go versions before 1.22, loop variables were reused across iterations:

// BUGGY in Go < 1.22
for i := 0; i < 3; i++ {
    go func() {
        fmt.Println(i) // captures i by reference
    }()
}
// Likely prints: 3, 3, 3 (not 0, 1, 2)

All three goroutines capture the same i. By the time they run, the loop has finished and i is 3.

The fix (works in all Go versions):

for i := 0; i < 3; i++ {
    go func(n int) {
        fmt.Println(n) // n is a copy, not a reference
    }(i) // pass i as argument - creates a copy
}

Go 1.22 changed the loop variable semantics so each iteration gets its own variable. But AI trained on older code still generates the buggy pattern, and plenty of codebases still target Go < 1.22.

AI pitfall
AI generates the loop capture bug in two common forms:
1. Goroutines inside loops without passing the variable as an argument
2. Deferred closures inside loops that reference the loop variable

Even if you're targeting Go 1.22+, the parameter-passing pattern is still clearer and worth requesting.
Go versionLoop variable behaviorSafe pattern
< 1.22Single variable reused across iterationsPass as function argument: go func(n int) { ... }(i)
>= 1.22New variable per iterationEither pattern works, but argument passing is clearer
05

IIFEs, immediately invoked functions

Sometimes you need to run a function immediately after defining it. This creates an isolated scopeWhat is scope?The area of your code where a variable is accessible; variables declared inside a function or block are invisible outside it.:

config := func() map[string]string {
    defaults := map[string]string{
        "host": "localhost",
        "port": "8080",
    }
    if envHost := os.Getenv("HOST"); envHost != "" {
        defaults["host"] = envHost
    }
    return defaults
}() // the () invokes it immediately

IIFEs keep temporary variables (defaults) out of the surrounding scope. AI uses this pattern for complex initialization where a simple assignment isn't enough.

06

When closures vs named functions

Closures add cognitive load because you have to trace captured variables. Use this as a guide when evaluating AI's choice:

SituationBetter choiceWhy
Short, used onceClosureNo need to name a throwaway function
Needs captured stateClosureThat's what closures are for
Complex logic (10+ lines)Named functionEasier to test, read, debug
Used in multiple placesNamed functionDRY, don't duplicate closures
Inside a performance-critical loopNamed functionAvoid repeated closure allocation
AI pitfall
AI tends to inline everything as closures, even complex multi-step logic. If a closure grows past 10-15 lines, ask AI to extract it into a named function. Named functions are testable; closures embedded in other functions are not.