Functions
Define reusable logic with func, return multiple values, use named returns, and discard unwanted values with the blank identifier.
- Define a function with func, typed parameters, and a return type
- Return multiple values from a single function
- Use the blank identifier _ to discard unwanted return values
- Write a variadic function that accepts any number of arguments
- Understand named return values and when to use them
The Fundamentals track describes functions as named, reusable units of logic. Go's func keyword gives you that — and then goes a step further with one of Go's most distinctive features: multiple return values. This isn't a trick or a special case; it's the primary mechanism for idiomatic error handling in Go, and you'll see it in virtually every Go program you read.
Defining a function
func add(a int, b int) int {
return a + b
}Breaking it down:
funcintroduces the function.addis the name.(a int, b int)are the parameters — every parameter requires an explicit type. Go does not infer function parameter types.- The final
intis the return type. returnexplicitly returns a value.
When consecutive parameters share a type, you can write the type once:
func add(a, b int) int {
return a + b
}Call it like any function in any language:
func main() {
result := add(3, 4)
fmt.Println(result) // 7
}Multiple return values
Go functions can return more than one value, and the language uses this heavily — especially for returning a result alongside an error:
func divide(a, b float64) (float64, error) {
if b == 0 {
return 0, fmt.Errorf("cannot divide by zero")
}
return a / b, nil
}On the calling side, you capture both values:
result, err := divide(10, 3)
if err != nil {
fmt.Println("Error:", err)
return
}
fmt.Printf("%.4f\n", result) // 3.3333This pattern — value, err := someFunc(...) followed by if err != nil — is the standard Go way to handle errors. It's explicit, it's readable, and it means error paths are always visible at the call site.
Why multiple returns instead of exceptions? Go deliberately omits exceptions. Returning an error as a plain value forces the caller to acknowledge it. You cannot accidentally ignore a thrown exception because there are no thrown exceptions. The if err != nil idiom is verbose by design — it keeps error handling visible.
The blank identifier
When a function returns multiple values but you only need some of them, use _ (the blank identifier) to discard the rest:
result, _ := divide(10, 3) // ignore the error (only do this when safe)
fmt.Println(result)_ is a write-only placeholder. You can assign to it, but you cannot read from it. The compiler enforces that every declared variable is used — _ is the escape hatch when a value is genuinely unneeded.
Discarding errors with _ is usually a bad habit. It's fine in small scripts or when you are absolutely certain the function cannot fail in your context, but in production code you should always check the error. Silently discarding an error is one of the most common causes of mysterious bugs in Go programs.
Named return values
Go allows you to name the return values in the function signature. Named returns declare variables that are in scope throughout the function body, and a bare return statement sends back their current values:
func minMax(values []int) (min, max int) {
min, max = values[0], values[0]
for _, v := range values {
if v < min {
min = v
}
if v > max {
max = v
}
}
return // returns min and max
}Named returns work well for short functions where the names document the return values clearly. For longer functions, explicit return value statements are usually clearer.
Variadic functions
A function can accept a variable number of arguments by prefixing the last parameter type with ...:
func sum(nums ...int) int {
total := 0
for _, n := range nums {
total += n
}
return total
}Call it with any number of arguments:
fmt.Println(sum(1, 2, 3)) // 6
fmt.Println(sum(10, 20, 30, 40)) // 100You can also spread a slice into a variadic call with ...:
numbers := []int{5, 6, 7}
fmt.Println(sum(numbers...)) // 18fmt.Println itself is variadic — that's why it can print any number of values in one call.
Check your understanding
Knowledge check
- 1.What is the idiomatic Go pattern for a function that might fail?
- 2.What does the blank identifier _ do in
result, _ := divide(10, 3)? - 3.Go infers parameter types in function definitions, so
func double(x) intis valid.
Do it yourself
Add this to main.go and run go run main.go:
func swap(a, b string) (string, string) {
return b, a
}
func main() {
first, second := swap("hello", "world")
fmt.Println(first, second) // world hello
}Then write a min function that accepts two int parameters and returns the smaller one. Call it from main and print the result. Use go vet ./... to check for any issues.
Where to go next
You can define functions, return multiple values, and handle errors the Go way. Next: control flow — making decisions with if, repeating work with for (Go's only loop keyword), and dispatching on values with switch.