# Why Multiple Return Values Matter
Multiple return values are one of Go's core language features. They remove hidden control flow and make success/failure explicit at the call site.
Instead of exceptions, Go returns values like (result, error). This forces callers to handle errors right where the function is used, which makes code easier to reason about during debugging and incident response.
Real-World Analogyclick to expand
Think of a bank transaction API returning two envelopes: one with the result and one with a status note. You always inspect the status note first before trusting the result envelope.
That is exactly the pattern in Go: value, err := fn() thenif err != nil handle failure, otherwise continue.
# The Core Pattern: (result, error)
The most common function signature in Go returns a useful value plus an error.
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, 2)
if err != nil {
log.Fatal(err)
}
fmt.Println(result) // 5# Step-by-Step: Caller-side Handling
1) Always capture both valuesIf a function returns two values, your assignment must receive two values. Example: v, err := parse().
2) Check error immediatelyKeep error checks adjacent to call sites. Delayed checks make logic brittle and can leak invalid values deeper into execution.
3) Use _ deliberately_ means “I intentionally ignore this value.” Use it only when ignoring is safe and documented by context.
# Named Return Values and Bare return
Named return values can improve readability for short functions and allow bare return. In long functions, prefer explicit returns for clarity.
func minMax(nums []int) (min, max int) {
min, max = nums[0], nums[0]
for _, n := range nums[1:] {
if n < min {
min = n
}
if n > max {
max = n
}
}
return // returns min, max
}# Discarding Values with _
Use blank identifier only when you intentionally don't need one of the values.
// Discard error only when guaranteed safe
result, _ := divide(10, 2)
fmt.Println(result)
// Discard result, only care about error
_, err := os.Stat("/etc/hosts")
if os.IsNotExist(err) {
fmt.Println("file not found")
}# Real-World Example: Parse and Validate API Input
This pattern is common in request handlers: parse input, validate range, and return either valid value or explicit error.
package main
import (
"fmt"
"strconv"
)
func ParseAge(s string) (int, error) {
age, err := strconv.Atoi(s)
if err != nil {
return 0, fmt.Errorf("invalid age %q: %w", s, err)
}
if age < 0 || age > 150 {
return 0, fmt.Errorf("age out of range: %d", age)
}
return age, nil
}
func main() {
age, err := ParseAge("25")
fmt.Println(age, err)
age, err = ParseAge("abc")
fmt.Println(age, err)
age, err = ParseAge("-5")
fmt.Println(age, err)
}⚡ Key Takeaways
- Multiple returns make success and failure explicit at the call site
- The standard Go shape is
(value, error) - Check
errimmediately after each call - Use named returns mainly for short/documentation-focused functions
- Use
_only when ignoring values is intentional and safe