Functions, multiple returns, and the err pattern
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
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.
amount, err := parsePaise("499.00")
if err != nil {
return fmt.Errorf("invalid amount: %w", err)
}
// use amount hereEvery 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 justT. 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.
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.
type discountFn func(price int) int
func tenPercentOff(price int) int { return price - price/10 }
var apply discountFn = tenPercentOff
final := apply(1000) // 900Closures 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.
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.
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.
Watching quietly. Tap me if you want a tip.
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
What does the program print?
show answer
package main import ( "fmt" "strconv" ) func main() { n, _ := strconv.Atoi("42") fmt.Println(n * 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 }