LearnAI ToolsCareerPractice BuildsPlayContact
Lesson 2115 min read

Multiple Return Values

Learn Go's signature multiple return value feature and the ubiquitous (value, error) idiom used throughout the standard library.

Introduction

Many languages force you to bundle multiple results into a tuple, object, or out-parameter. Go builds multiple return values directly into the language, and this feature underlies one of Go's most recognizable patterns: functions that return both a result and an error, checked explicitly at every call site.

What You Will Learn
  • How to declare and return multiple values from a function.
  • The (value, error) idiom used throughout Go and its standard library.
  • How to ignore a return value you don't need with the blank identifier.
  • How named returns interact with multiple return values.

Returning Multiple Values

To return more than one value, wrap the return types in parentheses, and separate the values in your return statement with commas.

package main
import "fmt"
func minMax(numbers []int) (int, int) {
min, max := numbers[0], numbers[0]
for _, n := range numbers {
if n < min {
min = n
}
if n > max {
max = n
}
}
return min, max
}
func main() {
smallest, largest := minMax([]int{4, 8, 1, 9, 3})
fmt.Println("smallest:", smallest)
fmt.Println("largest:", largest)
}
Output

Click Run to see what this code prints.

The (value, error) Idiom

Go doesn't have exceptions for regular error handling. Instead, functions that can fail conventionally return two values: the result, and an error that is nil on success and non-nil on failure. Callers are expected to check the error immediately, right after the call.

package main
import (
"errors"
"fmt"
)
func safeDivide(a, b float64) (float64, error) {
if b == 0 {
return 0, errors.New("division by zero")
}
return a / b, nil
}
func main() {
result, err := safeDivide(10, 2)
if err != nil {
fmt.Println("Error:", err)
} else {
fmt.Println("Result:", result)
}
result, err = safeDivide(10, 0)
if err != nil {
fmt.Println("Error:", err)
} else {
fmt.Println("Result:", result)
}
}
Output

Click Run to see what this code prints.

A Preview of the Next Chapter

This lesson focuses on the mechanics of multiple return values. A dedicated lesson on error handling later in the course goes much deeper into designing, wrapping, and comparing errors idiomatically.

Ignoring Return Values With _

Go requires that every declared variable be used, but sometimes you only care about some of a function's return values. The blank identifier, written as an underscore (_), lets you explicitly discard a value you don't need without triggering a "declared and not used" compile error.

package main
import "fmt"
func coordinates() (int, int, string) {
return 10, 20, "origin-offset"
}
func main() {
x, y, _ := coordinates() // ignore the label
fmt.Println(x, y)
_, _, label := coordinates() // only care about the label
fmt.Println(label)
}
Output

Click Run to see what this code prints.

Combining With Named Returns

Named return values work naturally with multiple returns, and can make functions with several output values considerably easier to read at a glance.

package main
import "fmt"
func parseCoordinates(input string) (x, y int, ok bool) {
// Simplified example — a real parser would use strconv and validation.
if input == "3,4" {
return 3, 4, true
}
return 0, 0, false
}
func main() {
x, y, ok := parseCoordinates("3,4")
fmt.Println(x, y, ok)
x, y, ok = parseCoordinates("invalid")
fmt.Println(x, y, ok)
}
Output

Click Run to see what this code prints.

Common Mistakes

Avoid These Mistakes
  • Ignoring the error return value entirely with a blanket _ — this silently hides failures that should be handled.
  • Checking err != nil after already using the result, instead of checking it immediately after the call.
  • Returning a non-nil error alongside a "successful-looking" value, which can confuse callers about what actually happened.
  • Forgetting that all declared, non-blank variables must be used somewhere, even from multi-value returns.
  • Overusing named returns for functions with many return values, making the function body harder to trace.

Best Practices

  • Always check an error return value immediately, right after the function call that produced it.
  • Return a zero value alongside a non-nil error, so callers who accidentally skip the error check still get a sane value.
  • Use the blank identifier _ deliberately and sparingly — never as a default habit for ignoring errors.
  • Keep the order of return values consistent with Go convention: primary result(s) first, error last.
  • Document what each return value means when a function returns more than two values.

Frequently Asked Questions

There's no hard language limit, but idiomatic Go rarely returns more than two or three values. If you need more, consider returning a struct instead.

It makes error handling explicit and visible at every call site, rather than allowing errors to silently propagate up an invisible call stack, which the Go designers felt made control flow harder to reason about.

Yes — if you call a function purely for its side effects and don't assign the results to any variable, that's valid, though for (value, error) functions this is almost always poor practice.

Key Takeaways

  • Go functions can return multiple values by listing types in parentheses after the parameter list.
  • The (value, error) pattern is Go's idiomatic replacement for exceptions.
  • The blank identifier _ lets you discard return values you don't need.
  • Named returns can improve readability for functions with several output values.
  • Errors should be checked immediately after the call that can produce them.

Summary

Multiple return values are one of Go's defining features, and the (value, error) idiom built on top of them shapes how nearly all Go code handles failure. You'll see this pattern in almost every standard library function from here on. Next, you'll learn about variadic functions, which let you accept a flexible number of arguments.

Next Lesson →

Variadic Functions