A function groups reusable logic behind a name. You declare one with the func
keyword, list its typed parameters, and state the types it returns. Functions
are the main unit of reuse in Go, and they are values too.
Declaring a function
The parameter list comes first, then the return type, then the body. Use
return to send a value back.
func greet(name string) string {
return "Hello, " + name
}
When several parameters share a type, you can write the type once at the end:
func add(a, b int) int {
return a + b
}
Multiple return values
Go functions can return more than one value. This is used constantly, most often to return a result alongside a status.
func minMax(a, b int) (int, int) {
if a < b {
return a, b
}
return b, a
}
The caller receives both values with a single call: lo, hi := minMax(9, 4).
Named returns (use sparingly)
You can name the return values. They act like variables initialised to their
zero value, and a bare return sends their current values back.
func split(sum int) (x, y int) {
x = sum * 4 / 9
y = sum - x
return
}
Named returns can document intent, but a bare return in a long function is
easy to misread. Prefer explicit returns unless the names genuinely add clarity.
Variadic functions
A final parameter written ...T accepts any number of arguments, which arrive
as a slice.
func sum(nums ...int) int {
total := 0
for _, n := range nums {
total += n
}
return total
}
Call it as sum(1, 2, 3), or spread an existing slice with sum(values...).
Functions as values and closures
Functions are first-class values: you can store them in variables and return them. A function that captures variables from its surrounding scope is a closure.
func counter() func() int {
count := 0
return func() int {
count++
return count
}
}
Each call to counter() returns a fresh function with its own private count.
The result-error convention
Idiomatic Go reports failure by returning a value and an error. By
convention the error is the last return value, and it is nil on success. The
caller checks it before using the result.
package main
import (
"errors"
"fmt"
)
// divide returns the integer quotient of a by b.
// It returns an error when b is zero.
func divide(a, b int) (int, error) {
if b == 0 {
return 0, errors.New("division by zero")
}
return a / b, nil
}
func main() {
if q, err := divide(10, 2); err == nil {
fmt.Println("10 / 2 =", q)
}
if _, err := divide(1, 0); err != nil {
fmt.Println("error:", err)
}
}
Running it prints:
10 / 2 = 5
error: division by zero
Common mistakes
- Ignoring the returned error. If a function returns
(T, error), check theerror; do not discard it with_unless you are certain it cannot occur. - Returning a useful value alongside a non-nil error. On failure, return the zero value so callers are not tempted to use garbage.
- Overusing named returns. In long functions a bare
returnhides what is actually sent back. Name returns only when it improves clarity.
Practice
Write a function average(nums ...float64) float64 that returns the mean of its
arguments, and 0 when called with none. Print the average of 4, 8, 15, 16, 23, 42 with two decimals.
Show the solution
package main
import "fmt"
func average(nums ...float64) float64 {
if len(nums) == 0 {
return 0
}
var sum float64
for _, n := range nums {
sum += n
}
return sum / float64(len(nums))
}
func main() {
fmt.Printf("%.2f\n", average(4, 8, 15, 16, 23, 42))
}Guarding against the empty case avoids a division by zero, and dividing the sum
by the count gives 18.00.
Summary
- Declare functions with
func, typed parameters, and typed returns. - Multiple return values power the
(result, error)convention. - Variadic parameters and closures make functions flexible; keep them small.
Next, dig into the idiom that shapes Go APIs with error handling, and revisit how values are stored in variables and types.