Functions
📋 Jump to TakeawaysGo functions can return multiple values. That single design decision eliminated the need for exceptions, try/catch blocks, and the entire class of "forgot to handle the error" bugs. It's also why Go code tends to be explicit about what can go wrong, and that's a feature, not a burden.
Basic Functions
func add(a, b int) int {
return a + b
}
fmt.Println(add(3, 5)) // 8When consecutive parameters share a type, you declare the type once.
Multiple Return Values
Functions can return multiple values. This is how Go handles errors instead of using exceptions.
func divide(a, b float64) (float64, error) {
if b == 0 {
return 0, fmt.Errorf("division by zero")
}
return a / b, nil
}
result, err := divide(10, 3)
if err != nil {
fmt.Println(err)
}
fmt.Println(result) // 3.3333333333333335Named Return Values
Named returns act as pre-declared variables. A bare return sends them back.
func divide(a, b float64) (result float64, err error) {
if b == 0 {
err = fmt.Errorf("division by zero")
return
}
result = a / b
return
}Use named returns sparingly. They help in short functions but hurt readability in long ones.
Variadic Functions
The ... syntax accepts zero or more arguments of the same type.
func sum(nums ...int) int {
total := 0
for _, n := range nums {
total += n
}
return total
}
fmt.Println(sum(1, 2, 3)) // 6
fmt.Println(sum()) // 0
nums := []int{4, 5, 6}
fmt.Println(sum(nums...)) // 15 — spread a slice with ...Functions as Values
Functions are first-class values. Assign them to variables, pass them as arguments.
op := func(a, b int) int {
return a * b
}
fmt.Println(op(3, 4)) // 12
func apply(a, b int, fn func(int, int) int) int {
return fn(a, b)
}
fmt.Println(apply(5, 3, add)) // 8Anonymous Functions and Closures
An anonymous function is just a function without a name. A closure is a function that captures variables from its outer scope. They're different things, but in Go they almost always appear together.
// Anonymous function — no name, no captured variables
func() { fmt.Println("hello") }()
// Closure — captures `count` from outer scope
count := 0
increment := func() int {
count++
return count
}
fmt.Println(increment()) // 1
fmt.Println(increment()) // 2Closures are useful when you need a function that remembers state between calls, like counters, iterators, or middleware, without creating a struct.
func counter() func() int {
count := 0
return func() int {
count++
return count
}
}
next := counter()
fmt.Println(next()) // 1
fmt.Println(next()) // 2
fmt.Println(next()) // 3The returned function closes over count, keeping it alive between calls. Each call to counter() creates a new, independent count.
defer
defer says "run this later, right before the function exits." If you stack multiple defers, they run in reverse order: last one deferred runs first.
func readFile(path string) error {
f, err := os.Open(path)
if err != nil {
return err
}
defer f.Close() // guaranteed cleanup
// ... read file
return nil
}func main() {
defer fmt.Println("first")
defer fmt.Println("second")
defer fmt.Println("third")
// Output:
// third
// second
// first
}Common uses: closing files, unlocking mutexes, flushing buffers.
init() Function
init() runs automatically before main(). Any package can have an init() function, not just main. When you import a package, its init() runs before your code uses it.
Common uses: loading config, registering drivers, validating environment.
var config map[string]string
func init() {
config = map[string]string{
"env": "development",
}
fmt.Println("config loaded") // runs before main
}
func main() {
fmt.Println(config["env"]) // development
}A package can have multiple init() functions. They run in the order they appear in the file.
Key Takeaways
- Functions can return multiple values — the standard pattern for error handling
- Named returns are pre-declared variables; use sparingly
- Variadic functions accept zero or more args with
... - Functions are first-class: assign to variables, pass as arguments
- Closures capture and retain access to outer variables
deferruns on function exit in LIFO order — use for cleanupinit()runs beforemain()for package-level setup