Go (Golang) Tutorial 0/45 lessons ~6 min read Lesson 9

    Functions

    Functions are first-class in Go — they can be assigned to variables, passed as arguments, and returned from other functions.

    Course progress0%
    Focus
    10 guided sections
    Practice signal
    Examples included
    Career prep
    Interview Q&A included

    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.
    text
    flowchart LR
    Caller --> Params[Parameters]
    Params --> Body[Function Body]
    Body --> Return[Return Values]
    Return --> Caller

    Step-by-step explanation

    1. Caller passes arguments copied by value (or pointer copied).
    2. Function executes body; return sends values to caller.
    3. Variadic collects trailing args into slice; spread with fn(s...).
    4. Closures capture outer variables by reference.
    5. defer runs after return but before function exits to caller.
    6. 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:

    go
    package main
    import (
    "fmt"
    "time"
    )
    func retry(attempts int, delay time.Duration, fn func() error) error {
    var err error
    for 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 := 0
    for _, 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) string declares 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) error accepts another function as a parameter.
    • defer schedules 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 bare return.
    • 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?
    Copy of value; pointer parameter copies address — callee can mutate through pointer.
    Q2BeginnerWhat are variadic functions?
    Accept zero or more trailing args of same type as slice: func f(nums ...int).
    Q3Intermediatedefer execution order?
    LIFO — last deferred runs first; all run before function returns to caller.
    Q4IntermediateWhen use named return values?
    Short functions for documentation; avoid naked return in long functions.
    Q5AdvancedDesign idempotent payment capture function signature.
    func Capture(ctx context.Context, orderID string, idempotencyKey string) (Receipt, error) — document duplicate key returns existing receipt.

    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.

    Ready to mark this lesson complete?Track your journey across the entire course.