Skip to content

Functions

A Go function declares its parameter types and return types explicitly. The return type appears after the parameter list:

func add(a int, b int) int {
return a + b
}
// Consecutive parameters of the same type can share a type annotation
func add(a, b int) int {
return a + b
}

Go functions can return more than one value. The idiomatic pattern is (result, error) — the caller must handle or explicitly discard the error:

func divide(a, b float64) (float64, error) {
if b == 0 {
return 0, errors.New("division by zero")
}
return a / b, nil
}
result, err := divide(10, 3)
if err != nil {
log.Fatal(err)
}
fmt.Println(result)

Return values can be named. Named returns pre-declare variables and allow a bare return (a return with no arguments that returns the current values of the named variables). Use them sparingly — they are clearest in short functions where the names genuinely document the result:

func minMax(nums []int) (min, max int) {
min, max = nums[0], nums[0]
for _, v := range nums[1:] {
if v < min {
min = v
}
if v > max {
max = v
}
}
return // bare return: returns current min, max
}

A variadic function accepts a variable number of arguments of the same type. Declare the last parameter with ...:

func sum(nums ...int) int {
total := 0
for _, n := range nums {
total += n
}
return total
}
sum(1, 2, 3) // individual args
sum([]int{1,2,3}...) // spread a slice with ...

Inside the function, nums is a []int slice.

defer schedules a function call to run when the surrounding function returns, regardless of which return path is taken. It is the idiomatic way to clean up resources:

func readFile(name string) error {
f, err := os.Open(name)
if err != nil {
return err
}
defer f.Close() // runs when readFile returns
// ... work with f ...
return nil
}

Multiple deferred calls execute in LIFO order (last deferred, first to run). Arguments to the deferred function are evaluated immediately at the defer statement, not when the call runs.

Functions are first-class values in Go. You can assign them to variables, pass them as arguments, and return them from other functions:

double := func(n int) int { return n * 2 }
fmt.Println(double(5)) // 10

A closure is a function that captures variables from its enclosing scope. The captured variable is shared — the closure sees mutations to it:

func counter() func() int {
n := 0
return func() int {
n++
return n
}
}
c := counter()
fmt.Println(c()) // 1
fmt.Println(c()) // 2
fmt.Println(c()) // 3

Each call to counter() creates an independent n, so two counters do not interfere with each other.

package main
import (
"errors"
"fmt"
)
// Multiple return values
func divide(a, b float64) (float64, error) {
if b == 0 {
return 0, errors.New("division by zero")
}
return a / b, nil
}
// Variadic
func sum(nums ...int) int {
total := 0
for _, n := range nums {
total += n
}
return total
}
// Closure
func counter() func() int {
n := 0
return func() int {
n++
return n
}
}
// defer demo
func deferDemo() {
fmt.Println("deferDemo start")
defer fmt.Println("deferred 1 (runs last)")
defer fmt.Println("deferred 2 (runs second)")
defer fmt.Println("deferred 3 (runs first)")
fmt.Println("deferDemo end")
}
func main() {
// Multiple returns
result, err := divide(10, 3)
if err != nil {
fmt.Println("error:", err)
} else {
fmt.Printf("10 / 3 = %.4f\n", result)
}
_, err = divide(5, 0)
if err != nil {
fmt.Println("divide by zero:", err)
}
// Variadic
fmt.Println("sum(1..5) =", sum(1, 2, 3, 4, 5))
// Closure counter
c := counter()
fmt.Println("counter:", c(), c(), c())
// defer LIFO
deferDemo()
}
In Go, which idiomatic pattern do functions use when they can fail?
Three defer statements are executed in a function in order A, B, C. In what order do they run when the function returns?
What does a closure capture from its enclosing scope?
How do you pass a slice to a variadic function as individual arguments?