Go (Golang) Tutorial 0/45 lessons ~6 min read Lesson 10

    Multiple Return Values

    Go's signature feature is returning multiple values — typically (result, error).

    Course progress0%
    Focus
    10 guided sections
    Practice signal
    Examples included
    Career prep
    Interview Q&A included

    Introduction

    Go's signature feature is returning multiple values — typically (result, error). This idiom eliminates exceptions and makes failure paths explicit at every call site. The comma-ok pattern extends to map lookups and type assertions.

    Named return values enable naked returns in short functions but can reduce clarity in longer ones. Understanding when to return pointers vs values, and how to document multi-value returns in godoc, is essential for library design.

    Interviewers frequently ask why Go uses error returns instead of try/catch, and how to handle partial success scenarios — this lesson prepares you for both.

    The story

    A Cloudflare DNS API client fetches a record and its revision token in one call: record, etag, err := client.GetRecord(ctx, zoneID, name). Returning both data and error (plus metadata) is idiomatic Go — callers handle failures explicitly instead of throwing exceptions that crash goroutines in a server handling thousands of requests.

    Multiple return values eliminate out-parameters and global state, making gRPC handlers and database repositories easy to test with table-driven cases.

    Understanding the topic

    Key concepts

    • func f() (int, error) returns two values; caller: v, err := f().
    • Blank identifier discards: val, _ := map[key].
    • Comma-ok: val, ok := map[key]; ok is false if missing.
    • Named returns: (n int, err error) — initialized to zero values.
    • Early return with named: return (sets n and err automatically).
    • Return (zero, err) on failure; (result, nil) on success.

    Step-by-step explanation

    1. Function declares return types in signature.
    2. Caller must assign all returns or use blank _.
    3. Compiler enforces error handling less strictly than Rust — discipline required.
    4. Multiple values without error common: map lookup, type assertion, divMod.
    5. Return pointer + error: nil pointer signals failure alongside err.
    6. Wrap errors with fmt.Errorf("context: %w", err) for chain inspection.

    Practical code example

    Division with error return, comma-ok map lookup, and named returns:

    go
    package main
    import (
    "errors"
    "fmt"
    )
    func divide(a, b float64) (float64, error) {
    if b == 0 {
    return 0, errors.New("division by zero")
    }
    return a / b, nil
    }
    func lookupUser(users map[string]string, id string) (name string, found bool) {
    name, found = users[id]
    return // named return
    }
    func main() {
    result, err := divide(10, 2)
    if err != nil {
    fmt.Println("error:", err)
    return
    }
    fmt.Printf("10/2 = %.1f\n", result)
    users := map[string]string{"u1": "Alice", "u2": "Bob"}
    if name, ok := lookupUser(users, "u1"); ok {
    fmt.Println("found:", name)
    }
    if _, ok := lookupUser(users, "u99"); !ok {
    fmt.Println("user u99 not found")
    }
    }

    Output

    10/2 = 5.0
    found: Alice
    user u99 not found

    Line-by-line code explanation

    • func ParseConfig(path string) (Config, error) returns a value and an error — the Go idiom.
    • cfg, err := ParseConfig("app.yaml") assigns both returns in one statement.
    • if err != nil { return ..., fmt.Errorf("parse: %w", err) } wraps and propagates errors early.
    • return Config{}, err returns the zero value of Config alongside the error.
    • func divide(a, b int) (int, bool) returns a result and an ok boolean for soft failures.
    • result, ok := divide(10, 0) lets callers branch without panicking on invalid input.
    • named returns (n int, err error) improve readability in longer functions with multiple exit points.
    • _, err = io.Copy(dst, src) discards the byte count when only the error matters.

    Key takeaway: Always check err before using result. Comma-ok prevents zero-value confusion on map misses.

    Real-world use

    Where you'll use this in production

    • Every database and HTTP call returning (data, error).
    • Parsing functions: strconv.Atoi, json.Unmarshal.
    • Repository pattern: (Entity, error) or (nil, ErrNotFound).
    • Optional values without pointers via (T, bool) tuple.

    Best practices

    • Return (zero, err) on failure — never ignore err.
    • Use comma-ok for map/type assertions instead of panicking.
    • Limit named returns to short functions; prefer explicit return vals.
    • Document which return is nil on error for pointer returns.
    • Use errors.Is and errors.As for error chain inspection.
    • Return errors as last value consistently across package.

    Common mistakes

    • Using result before checking err — nil pointer dereference.
    • Returning err without context — wrap with %w.
    • Returning (nil, nil) — ambiguous success with no data.
    • Too many return values — use struct for 3+ related values.
    • Shadowing err in inner scope preventing outer check.

    Advanced interview questions

    Q1BeginnerWhy multiple return values?
    Explicit error handling without exceptions; comma-ok for optional results.
    Q2BeginnerComma-ok pattern?
    val, ok := map[key] — ok false if key missing; avoids zero-value ambiguity.
    Q3IntermediateNamed return pitfalls?
    Naked return in long functions obscures what's returned; defer can modify named returns.
    Q4IntermediateReturn pointer or value on error?
    Return (nil, err) on failure; document that result is nil when err != nil.
    Q5AdvancedHandle partial success in batch operation?
    Return (results []Result, err error) where err is first failure; or custom type with Errors []error and Successes []T.

    Summary

    (result, error) is Go's primary error idiom. Comma-ok handles map lookups and type assertions safely. Check err before using result; wrap errors with context. Named returns useful in short functions only. Next lesson: error handling — wrapping, sentinel errors, and errors package.

    Ready to mark this lesson complete?Track your journey across the entire course.