ToolPopToolPop
Go · Lesson 3 of 20

Functions, multiple returns, and the err pattern

6 min readUpdated 25 Jun 2026

Most languages give you one return value and an exception channel. Go gives you as many return values as you want, and the second one is almost always an error. Once this clicks, the rest of Go's design starts to make sense.

Function syntax, the short version

go
func add(a int, b int) int {
    return a + b
}
 
func divide(a, b float64) (float64, error) {
    if b == 0 {
        return 0, fmt.Errorf("divide by zero")
    }
    return a / b, nil
}

Three things to notice. Parameters of the same type can share a type annotation (a, b float64). Return types come after the parameters, not before. Multiple return values are wrapped in parentheses.

The (value, err) idiom

This is the heartbeat of Go.

go
amount, err := parsePaise("499.00")
if err != nil {
    return fmt.Errorf("invalid amount: %w", err)
}
// use amount here

Every meaningful call gives you back the result AND what went wrong. You handle both, every time. It is verbose. It is also why Go services keep running when something goes sideways at 11:47 PM during a Swiggy dinner rush.

Senior rule: if a function can fail, return (T, error). If it cannot fail, return just T. The signature is documentation.

You will see this pattern in every standard library function: os.Open, strconv.Atoi, json.Unmarshal, http.Get, all of them. Once your eyes train on it, Go code becomes very predictable to read.

When you do not care about one of the returns, assign it to _, the blank identifier. Go forces you to use every variable you declare, so _, err := strconv.Atoi(input) is how you say "yes I see it, no I do not need it". The same _ shows up in _, ok := m[k] map lookups and in side-effect-only imports like import _ "github.com/lib/pq" that register a database driver.

Named returns, the useful and the noisy

You can name your return values, which pre-declares them as variables inside the function.

go
func split(amount int) (gst, base int, err error) {
    if amount < 0 {
        err = fmt.Errorf("negative amount")
        return
    }
    base = amount * 100 / 118
    gst = amount - base
    return
}

Naked return sends back whatever the named vars currently hold. Looks tidy in three-line helpers. Becomes confusing in twenty-line functions because you cannot tell at a glance what is being returned.

Senior rule: use named returns for documentation (the names show up in go doc) and for tiny functions. Avoid naked returns in anything longer than ten lines.

Variadic, first-class, and closures

Three more shapes round out functions in Go.

A function can take a variable number of arguments using .... fmt.Println and append are the canonical examples. You write func sum(nums ...int) int and call it as sum(1, 2, 3) or spread a slice with sum(xs...). Inside the function, nums is just a []int.

Functions are first-class values. You can store them in variables, pass them as parameters, return them from other functions. This is how HTTP middleware works, how sort.Slice takes a comparator, how dependency injection happens without a framework.

go
type discountFn func(price int) int
 
func tenPercentOff(price int) int { return price - price/10 }
 
var apply discountFn = tenPercentOff
final := apply(1000) // 900

Closures are functions that capture variables from their surrounding scope. Same idea as JavaScript, fewer footguns. Go 1.22 made loop variables per-iteration by default, so the classic for i := range xs { go func() { use(i) }() } race is no longer a bug.

go
func counter() func() int {
    n := 0
    return func() int { n++; return n }
}

Each call to counter() makes a fresh n. The returned function carries it forward across calls. You will write these for rate limiters, retry helpers, and small stateful callbacks.

Diagram
rendering diagram...

Quick reference

Multiple return values
Go functions can return any number of values, typically (result, error).
Named returns
Pre-declared return variables. Allow naked return statements. Best used in small functions.
Variadic
A function that accepts a variable number of arguments using ...T syntax.
First-class function
A function that can be assigned, passed, and returned like any other value.
Closure
A function that captures variables from the scope it was defined in.
err idiom
The if err != nil { return ... } pattern that defines idiomatic Go error handling.

Next up, slices and maps. The two collections you will live in for the rest of your Go career.

Chai0/2 done

Watching quietly. Tap me if you want a tip.

Go Playground

Go cannot run natively in a browser. Run copies your code and opens go.dev/play ; paste and click Run there.

Try this (0 of 2 done)

  1. 1

    What does the program print?

    show answer
    package main
    
    import (
    	"fmt"
    	"strconv"
    )
    
    func main() {
    	n, _ := strconv.Atoi("42")
    	fmt.Println(n * 2)
    }
  2. 2

    What if the input is "abc" instead of "42"? Predict the output.

    hint

    strconv.Atoi returns an error for non-numeric strings.

    show answer
    n, err := strconv.Atoi("abc")
    if err != nil { fmt.Println("bad input"); return }