Functions
Functions are first-class in Go — they can be assigned to variables, passed as arguments, and returned from other functions.
Introduction
Functions are first-class in Go — they can be assigned to variables, passed as arguments, and returned from other functions. Go supports multiple return values, variadic parameters, and named return values with naked returns (used sparingly).
Understanding value vs pointer receivers, closure capture, and defer placement inside functions is critical for writing testable service layers. Functions are the primary unit of abstraction before you reach interfaces and methods on structs.
This lesson establishes patterns for small, focused functions with clear signatures — the style Google, Uber, and the Go standard library consistently demonstrate in production code.
The story
A Docker registry mirror service computes SHA-256 digests, validates manifest layers, and returns a signed URL — each step is a pure function that accepts inputs and returns outputs. When Uber refactors a pricing microservice, small functions with clear signatures make unit tests fast and diffs reviewable.
Functions are first-class values in Go: you pass a retry strategy into an HTTP client wrapper, or return a closure that captures a logger — patterns you'll see throughout the Kubernetes client-go library.
Understanding the topic
Key concepts
- func name(params) returnType { } — single or multiple return types.
- Parameters are pass-by-value; pointers pass address for mutation.
- Variadic: func sum(nums ...int) — nums is []int inside function.
- Named returns: func f() (result int, err error) — naked return assigns to names.
- Functions are values: var fn func(int) int = square.
- defer schedules call at function exit — LIFO order for multiple defers.
flowchart LRCaller --> Params[Parameters]Params --> Body[Function Body]Body --> Return[Return Values]Return --> Caller
Step-by-step explanation
- Caller passes arguments copied by value (or pointer copied).
- Function executes body; return sends values to caller.
- Variadic collects trailing args into slice; spread with fn(s...).
- Closures capture outer variables by reference.
- defer runs after return but before function exits to caller.
- Recursive functions need base case; Go has no tail-call optimization.
Practical code example
Retry wrapper using first-class functions, variadic args, and defer for timing:
package mainimport ("fmt""time")func retry(attempts int, delay time.Duration, fn func() error) error {var err errorfor i := 0; i < attempts; i++ {start := time.Now()err = fn()if err == nil {return nil}fmt.Printf("attempt %d failed after %v: %v\n", i+1, time.Since(start), err)time.Sleep(delay)}return fmt.Errorf("after %d attempts: %w", attempts, err)}func sum(nums ...int) int {total := 0for _, n := range nums {total += n}return total}func main() {_ = retry(3, 100*time.Millisecond, func() error {return fmt.Errorf("simulated failure")})fmt.Println("sum:", sum(1, 2, 3, 4))}
Line-by-line code explanation
func Digest(data []byte) stringdeclares a function with typed parameters and return type.return hex.EncodeToString(hash[:])returns the computed digest from the function body.func Retry(fn func() error, attempts int) erroraccepts another function as a parameter.deferschedules cleanup when the surrounding function returns — common for closing connections.func makeLogger(service string) func(string)returns a closure capturing the service name.named return values (result string, err error)let you assign in the body and barereturn.variadic func Log(fields ...string)accepts zero or more trailing arguments.methods vs functions— functions live at package level; methods attach to types (covered next).
Key takeaway: Pass func() error for retryable operations. Variadic ...int accepts zero or more ints. defer is covered deeply with defer+recover in error lesson.
Real-world use
Where you'll use this in production
- Middleware functions wrapping http.Handler.
- Strategy functions passed to sort.Slice.
- Retry and timeout wrappers in HTTP and database clients.
- Functional options pattern for configuring structs.
Best practices
- Keep functions under ~40 lines; extract helpers.
- Prefer explicit returns over naked returns except tiny functions.
- Use defer for cleanup: file.Close(), mutex.Unlock(), conn.Close().
- Accept interfaces, return concrete types in public APIs.
- Document exported functions with godoc comments.
- Avoid defer in tight loops — schedules one defer per iteration (costly).
Common mistakes
- Defer in loop — accumulates defers until function returns.
- Naked return with long functions — confusing which values return.
- Capturing loop variable in closure before Go 1.22 fix.
- Too many parameters — use options struct or functional options.
- Ignoring variadic nil — fn(nil...) passes nil slice, not empty.
Advanced interview questions
Q1BeginnerPass by value in Go?
Q2BeginnerWhat are variadic functions?
Q3Intermediatedefer execution order?
Q4IntermediateWhen use named return values?
Q5AdvancedDesign idempotent payment capture function signature.
Summary
Functions support multiple returns, variadic args, and first-class values. defer ensures cleanup runs on any exit path. Keep functions small; use options pattern for many parameters. Closures capture outer variables — mind loop variable scoping. Next lesson: multiple return values and the error idiom.