> series: learn go from 0 to 100 | article 4 of 19
Go functions look like any language’s functions until they return two things at once and nothing weird happens. Today: syntax, multiple returns, variadics, closures and defer, your open resources’ best friend.
Syntax: the type goes after
// type comes after the name
func add(a int, b int) int {
return a + b
}
// consecutive params of the same type can be grouped
func add(a, b int) int {
return a + b
}
The type after the name feels odd for ten minutes, then reads better, especially in complex signatures. The official rationale is on the Go blog (Go’s Declaration Syntax). Another difference from Java: no function overloading. One name, one signature (official FAQ).
Multiple returns: the pattern that changes everything
A Go function can return several values with no wrappers: no Python tuples, no JS destructured arrays, no Result classes (A Tour of Go, multiple results and Effective Go):
// Two values back: result and error.
// This is THE Go pattern. Tattoo it somewhere.
func divide(a, b float64) (float64, error) {
if b == 0 {
return 0, errors.New("division by zero")
}
return a / b, nil
}
// At the call site:
result, err := divide(10, 3)
if err != nil {
// handle and return early
}
That (value, error) duo is the backbone of Go error handling and gets its own article (number 9). For now, remember the shape: the error comes back as a regular value and gets checked with an if right after.
Return values can also be named. Use sparingly, in short functions it documents, in long ones it confuses.
Variadics: the three dots
The equivalent of Python’s *args or JS rest parameters (spec, function types):
// ...int: zero or more ints. Inside it's a slice.
func max(nums ...int) int {
m := nums[0]
for _, n := range nums[1:] {
if n > m {
m = n
}
}
return m
}
max(3, 7, 2)
values := []int{3, 7, 2}
max(values...) // spread a slice with ...
Inside the function, nums is a regular slice. Note the last line: … also works in reverse, spreading a slice into individual arguments. fmt.Println is variadic, that’s why it swallows anything.
Closures: functions with a backpack
Functions are first-class values: assign them, pass them, return them. When a returned function captures variables from its environment, you’ve got a closure (A Tour of Go, closures):
func counter() func() int {
n := 0
return func() int {
n++ // captures n by reference
return n
}
}
c1 := counter()
c2 := counter()
fmt.Println(c1(), c1(), c1()) // 1 2 3
fmt.Println(c2()) // 1 (its own state)
Each call to counter() creates a fresh, independent n that survives between calls. Coming from JS this is familiar ground. Coming from Java, it’s what lambdas wanted to be when they grew up: capture is by reference, no final required.
defer: guaranteed cleanup
defer postpones a call until the enclosing function returns, by any path, including an early error return (A Tour of Go, defer and Effective Go, defer):
func readFile(path string) error {
f, err := os.Open(path)
if err != nil {
return err
}
defer f.Close() // runs on exit, no matter what
// ... work with f in peace ...
return nil
}
It’s Java’s try/finally or Python’s with, but glued to the line that opens the resource: you open, and on the very next line you schedule the close. Impossible to forget 40 lines later, which is why defer is one of the most defensive corners of Go functions.
Key detail: stacked defer calls run in LIFO order, last registered runs first. And arguments are evaluated when the defer line runs, not when the call executes.

Weekly challenge: put Go functions to the test.
- Write divide(a, b float64) (float64, error) and handle division by zero at the call site.
- Create a variadic average(nums …float64) float64 and call it with loose arguments and with a spread slice.
- Build a closure accumulator() that adds up whatever you pass on each call, and prove two accumulators don’t share state.
- Bonus: stack three defer calls with fmt.Println and predict the output order before running.
Go functions: level complete.
Next article: structs, methods, and why Go doesn’t need classes to live a full life.
> exit