How does Go handle multiple return values, and what's the idiomatic use for the error return?

4 minbeginnergoerror-handlingmultiple-returnsfundamentals

Quick Answer

Go functions can return more than one value, most commonly a result plus an error: func Divide(a, b int) (int, error). The idiom is to check the error immediately after the call, before touching the result: result, err := Divide(a, b); if err != nil { return err }. There's no exception mechanism in normal Go control flow — errors are just ordinary values, returned and checked explicitly at every call site. This makes error paths visible in the code itself, at the cost of more if err != nil boilerplate than languages with try/catch. Multiple returns are also used for patterns like the map "comma ok" idiom (v, ok := m[k]) and type assertions (v, ok := x.(T)).

Detailed Answer

Go deliberately has no exceptions for normal control flow. Multiple return values are the mechanism that replaces them.

The basic pattern

func Divide(a, b int) (int, error) {
    if b == 0 {
        return 0, fmt.Errorf("divide by zero")
    }
    return a / b, nil
}

result, err := Divide(10, 0)
if err != nil {
    log.Fatal(err)
}
fmt.Println(result)

Named return values

func Divide(a, b int) (result int, err error) {
    if b == 0 {
        err = fmt.Errorf("divide by zero")
        return  // "naked" return — sends back the named result and err
    }
    result = a / b
    return
}

Named returns can make a function's intent clearer, and let a deferred function modify the return value before it's actually sent back — a common pattern for adding context to an error right before returning.

Other uses of multiple returns

v, ok := m["key"]          // map lookup: value + "was it present"
n, err := f.Read(buf)      // io.Reader: bytes read + error
val, ok := x.(string)      // type assertion: value + "did it succeed"

Why this beats exceptions, in Go's design philosophy

Every place an error can occur is visible directly in the function signature and at the call site. Nothing is hidden behind an invisible stack unwind. The tradeoff is verbosity. Real Go code is full of if err != nil { return err }. The language accepts that cost, to keep failure paths explicit rather than implicit.