Code of the Day
BeginnerCore syntax

Functions

Define reusable logic with func, return multiple values, use named returns, and discard unwanted values with the blank identifier.

GoBeginner10 min read
Recommended first
By the end of this lesson you will be able to:
  • 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:

  • func introduces the function.
  • add is the name.
  • (a int, b int) are the parameters — every parameter requires an explicit type. Go does not infer function parameter types.
  • The final int is the return type.
  • return explicitly 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.3333

This 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 ) 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)) // 100

You can also spread a slice into a variadic call with ...:

numbers := []int{5, 6, 7}
fmt.Println(sum(numbers...))   // 18

fmt.Println itself is variadic — that's why it can print any number of values in one call.

Check your understanding

Knowledge check

  1. 1.
    What is the idiomatic Go pattern for a function that might fail?
  2. 2.
    What does the blank identifier _ do in result, _ := divide(10, 3)?
  3. 3.
    Go infers parameter types in function definitions, so func double(x) int is 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.

Finished reading? Mark it complete to track your progress.

On this page