Skip to content

Go Functions — func Multiple Returns Named Returns and Variadic Parameters

DodaTech Updated 2026-06-28 7 min read

In this tutorial, you will learn about Go Functions. We cover key concepts, practical examples, and best practices to help you master this topic.

Go functions defined with func support multiple return values for error handling, named returns for self-documenting code, variadic parameters with ... syntax, and first-class function values for callbacks.

What You'll Learn

  • Defining functions with parameters and returns
  • Multiple return values and named returns
  • Variadic functions with ...
  • First-class functions and closures

Why It Matters

Functions are the building blocks of Go programs. Docker uses functions for each CLI command, Kubernetes controllers are built from function chains. Doda Browser uses functions for HTTP handlers and middleware. Understanding Go's function patterns is essential.

flowchart LR
    A["Functions"] --> B["Declaration"]
    B --> C["Returns"]
    C --> D["Multiple Returns"]
    D --> E["Variadic"]
    A:::current --> B
    style A fill:#2563eb,stroke:#2563eb,color:#fff
    style B fill:#dbeafe,stroke:#2563eb,color:#1e40af
    style C fill:#dbeafe,stroke:#2563eb,color:#1e40af
    style D fill:#dbeafe,stroke:#2563eb,color:#1e40af
    style E fill:#f1f5f9,stroke:#94a3b8,color:#64748b

Basic Function Declaration

package main

import "fmt"

func greet(name string) string {
    return "Hello, " + name + "!"
}

func main() {
    msg := greet("Alice")
    fmt.Println(msg)
}

Multiple Return Values

Go functions can return multiple values, commonly used for returning results with errors:

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

func main() {
    result, err := divide(10, 2)
    if err != nil {
        fmt.Println("Error:", err)
        return
    }
    fmt.Println("Result:", result)
}

Ignoring Return Values

result, _ := divide(10, 2)  // Ignore error (not recommended)

Named Return Values

Named returns document the meaning of each return value:

func rectangle(width, height float64) (area float64, perimeter float64) {
    area = width * height
    perimeter = 2 * (width + height)
    return  // Naked return — returns area and perimeter
}

func main() {
    a, p := rectangle(5, 3)
    fmt.Println("Area:", a)
    fmt.Println("Perimeter:", p)
}

Named returns are initialized to their zero values and a bare return returns them.

Variadic Functions

Functions that accept a variable number of arguments:

func sum(numbers ...int) int {
    total := 0
    for _, n := range numbers {
        total += n
    }
    return total
}

func main() {
    fmt.Println(sum(1, 2))           // 3
    fmt.Println(sum(1, 2, 3, 4, 5)) // 15

    // Slice to variadic
    nums := []int{10, 20, 30}
    fmt.Println(sum(nums...))        // 60
}

Function Values

Functions are first-class in Go — they can be assigned to variables, passed as arguments, and returned:

func main() {
    // Assign function to variable
    double := func(x int) int {
        return x * 2
    }

    fmt.Println(double(5))  // 10

    // Pass function as argument
    numbers := []int{1, 2, 3, 4, 5}
    transformed := mapValues(numbers, double)
    fmt.Println(transformed)  // [2 4 6 8 10]
}

func mapValues(values []int, f func(int) int) []int {
    result := make([]int, len(values))
    for i, v := range values {
        result[i] = f(v)
    }
    return result
}

Closures

Functions that capture their surrounding scope:

func counter() func() int {
    count := 0
    return func() int {
        count++
        return count
    }
}

func main() {
    c1 := counter()
    c2 := counter()

    fmt.Println(c1())  // 1
    fmt.Println(c1())  // 2
    fmt.Println(c2())  // 1 (independent counter)
}

Anonymous Functions

Functions without a name, often used for goroutines or short callbacks:

func main() {
    // Inline anonymous function
    result := func(a, b int) int {
        return a + b
    }(3, 4)

    fmt.Println(result)  // 7

    // Goroutine with anonymous function
    go func() {
        fmt.Println("Running in goroutine")
    }()
}

Deferred Function Calls

Defer schedules execution until the surrounding function returns:

func processFile(name string) error {
    f, err := os.Open(name)
    if err != nil {
        return err
    }
    defer f.Close()  // Runs when processFile returns

    // Process file...
    return nil
}

Deferred functions are executed in LIFO order and can access named return values:

func countLines(path string) (lines int, err error) {
    f, err := os.Open(path)
    if err != nil {
        return 0, err
    }
    defer f.Close()

    defer func() {
        fmt.Printf("File %s has %d lines\n", path, lines)
    }()

    scanner := bufio.NewScanner(f)
    for scanner.Scan() {
        lines++
    }
    return lines, scanner.Err()
}

Common Mistakes

1. Unused Parameters

// Won't compile
func greet(name string, unused int) string {
    return "Hello, " + name
}
// Use _ for unused parameters
func greet(name string, _ int) string {
    return "Hello, " + name
}

2. Shadowing Variables with :=

func example() error {
    x, err := doSomething()
    if err != nil {
        return err
    }
    // Forgot to use x...

    x, err = doSomethingElse()  // := would create new shadow variables
    return err
}

3. Naked Returns with Complex Logic

Naked returns (return without values) are only clear for short functions. In long functions, explicit returns improve readability.

4. Not Checking Returned Errors

result, _ := doSomething()  // Error ignored!

5. Confusing Parameter Types with Return Types

// Return type comes after parameters
func add(a, b int) int { ... }

Practice Questions

1. How do you return multiple values from a Go function?

List multiple types in the return signature: func divide(a, b float64) (float64, error). Callers destructure with result, err := divide(1, 2).

2. What are named return values?

Named return values are declared in the function signature with names. They are initialized to zero values and can be returned with a bare return statement.

3. How do variadic functions work?

Variadic functions accept any number of trailing arguments using ...: func sum(nums ...int) int. The arguments become a slice inside the function.

4. What is a closure?

A closure is a function value that references variables from outside its body. The function captures the variables and can access them even when the enclosing function has returned.

Challenge: Write a function that takes a slice of integers and a predicate function, returning two slices: those matching and those not matching.

Solution
package main

import "fmt"

func partition(nums []int, pred func(int) bool) ([]int, []int) {
    var match, rest []int
    for _, n := range nums {
        if pred(n) {
            match = append(match, n)
        } else {
            rest = append(rest, n)
        }
    }
    return match, rest
}

func main() {
    nums := []int{1, 2, 3, 4, 5, 6}
    evens, odds := partition(nums, func(n int) bool {
        return n%2 == 0
    })
    fmt.Println("Evens:", evens)
    fmt.Println("Odds:", odds)
}

FAQ

{{< faq question="Can I have default parameter values in Go?" >}} No. Go doesn't support default parameter values. Use variadic options or helper constructor functions instead. {{< /faq >}}

{{< faq question="What's the difference between parameters and arguments?" >}} Parameters are the variables in the function definition. Arguments are the values passed when calling the function. {{< /faq >}}

{{< faq question="Can I overload functions in Go?" >}} No. Go doesn't support function overloading. Each function must have a unique name within a package. {{< /faq >}}

{{< faq question="How do I define a function inside another function?" >} Use an anonymous function assigned to a variable or defined inline. Nested functions must be anonymous — you can't declare a named function inside another. {{< /faq >}}

{{< faq question="What is init function?" >} init() is a special function that runs automatically before main(). It's used for initialization and doesn't take parameters or return values. You can have multiple init functions in a package. {{< /faq >}}

Try It Yourself

package main

import (
    "fmt"
    "strings"
)

func transform(words []string, fn func(string) string) []string {
    result := make([]string, len(words))
    for i, w := range words {
        result[i] = fn(w)
    }
    return result
}

func main() {
    words := []string{"hello", "world", "go", "programming"}

    upper := transform(words, strings.ToUpper)
    fmt.Println("Upper:", upper)

    prefix := transform(words, func(s string) string {
        return "!!" + s + "!!"
    })
    fmt.Println("Prefixed:", prefix)

    // Counter closure
    counter := func() func() int {
        count := 0
        return func() int {
            count++
            return count
        }
    }()

    fmt.Println("Count:", counter())
    fmt.Println("Count:", counter())
    fmt.Println("Count:", counter())
}

Expected output:

Upper: [HELLO WORLD GO PROGRAMMING]
Prefixed: [!!hello!! !!world!! !!go!! !!programming!!]
Count: 1
Count: 2
Count: 3

What's Next

Now that you understand functions, learn about arrays and slices for working with collections.

Topic Description Link
Go Arrays & Slices Fixed arrays, slice, append, make {{< ref "07-arrays-slices" >}}
Go Maps & Structs map, struct, tags, zero values {{< ref "08-maps-structs" >}}
Rust Functions Compare Rust functions Rust

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro

Home Browse Go