Functions & Multiple Returns
A function is a named block of code that does one job. You have already used many: fmt.Println, strconv.Atoi, and your own main. Go functions have one feature that most languages lack: they can return several values at once, which is how Go reports errors without exceptions.
In this lesson you will write functions with parameters and results, return multiple values, accept any number of arguments, treat functions as values, build closures, and clean up with defer.
Defining and calling functions
A function starts with func, then its name, its parameters in brackets (each with a type), and the type of the result. When two parameters in a row share a type, you can write the type once.
package main
import "fmt"
func splitBill(total, people int) (int, int) {
return total / people, total % people
}
func main() {
each, extra := splitBill(1150, 4)
fmt.Println("Each pays:", each)
fmt.Println("Left over:", extra)
}Each pays: 287 Left over: 2
splitBill returns two values, listed in brackets after the parameters: (int, int). The caller receives both with each, extra := .... Four friends in Pune each pay ₹287, and ₹2 is left for the tip jar.
If you do not need one of the results, ignore it with the blank identifier: each, _ := splitBill(1150, 4). Go does not allow you to simply drop it, which makes ignored results visible in code reviews.
The (value, error) pattern
The most important use of multiple returns is error handling. A function that can fail returns its normal result plus an error. If everything worked, the error is nil:
package main
import "fmt"
func ticketPrice(class string) (int, error) {
switch class {
case "sleeper":
return 450, nil
case "ac3":
return 1200, nil
}
return 0, fmt.Errorf("unknown class %q", class)
}
func main() {
for _, c := range []string{"ac3", "ac5"} {
price, err := ticketPrice(c)
if err != nil {
fmt.Println("Error:", err)
continue
}
fmt.Println(c, "costs", price)
}
}ac3 costs 1200 Error: unknown class "ac5"
fmt.Errorf builds an error with a formatted message. The caller checks err != nil straight away and handles the problem before using price. You will see this shape in nearly every Go program; Errors: Wrapping, Is & As covers it in depth.
Named results
You can give the results names. They start at their zero values, act as normal variables inside the function, and a bare return sends back their current values:
package main
import "fmt"
func marksRange(marks []int) (lowest, highest int) {
lowest, highest = marks[0], marks[0]
for _, m := range marks {
lowest = min(lowest, m)
highest = max(highest, m)
}
return
}
func main() {
lo, hi := marksRange([]int{67, 91, 45, 88})
fmt.Println("Lowest:", lo, "Highest:", hi)
}Lowest: 45 Highest: 91
min and max are built-in functions (added in Go 1.21). Named results are good documentation: the signature now says which number is which. Use the bare return only in short functions; in long ones, write return lowest, highest so readers do not have to search for the values.
Variadic functions
A variadic function accepts any number of arguments of one type. Put ... before the type of the last parameter. Inside, the parameter is a slice. To pass an existing slice, add ... after it in the call:
package main
import "fmt"
func total(prices ...int) int {
sum := 0
for _, p := range prices {
sum += p
}
return sum
}
func main() {
fmt.Println(total())
fmt.Println(total(40, 25, 60))
cart := []int{199, 349}
fmt.Println(total(cart...))
}0 125 548
fmt.Println itself is variadic, which is why it accepts any number of values.
Functions as values and closures
In Go, functions are values. You can store them in variables, pass them to other functions and return them. A function written without a name is an anonymous function:
package main
import "fmt"
func applyOffer(price int, offer func(int) int) int {
return offer(price)
}
func tokenMaker(prefix string) func() string {
n := 0
return func() string {
n++
return fmt.Sprintf("%s%d", prefix, n)
}
}
func main() {
festive := func(p int) int { return p * 90 / 100 }
flatOff := func(p int) int { return p - 50 }
fmt.Println(applyOffer(1000, festive), applyOffer(1000, flatOff))
nextToken := tokenMaker("A")
fmt.Println(nextToken(), nextToken(), nextToken())
}900 950 A1 A2 A3
The type func(int) int means "a function that takes an int and returns an int". The function returned by tokenMaker is a closure: it remembers the variable n from the function that created it, and each call increases the same n. A clinic in Lucknow could use one maker per counter to hand out tokens A1, A2 and so on.
defer: run something when the function ends
defer schedules a function call to run when the surrounding function returns, however it returns. It is Go's tool for cleanup: closing files, unlocking locks, stopping timers. Deferred calls run in reverse order (last in, first out), and their arguments are evaluated at the moment you write defer.
package main
import "fmt"
func serve(customer string) {
fmt.Println("Open counter for", customer)
defer fmt.Println("Close counter for", customer)
defer fmt.Println("Print receipt for", customer)
fmt.Println("Serving", customer)
}
func main() {
serve("Isha")
x := 1
defer fmt.Println("deferred x =", x)
x = 2
fmt.Println("x =", x)
}Open counter for Isha Serving Isha Print receipt for Isha Close counter for Isha x = 2 deferred x = 1
The receipt prints before the counter closes because deferred calls run in reverse. The last line shows 1, not 2, because the argument x was copied when defer ran. A typical real use is f, err := os.Open(name) followed by defer f.Close() right after the error check.
Common mistakes
- Ignoring the error result.
price, _ := ticketPrice(c)hides failures; handleerrunless you are sure it cannot happen. - Expecting default or keyword arguments. Go has neither, and no function overloading either. Use different function names or an options struct.
- Deferring inside a long loop. Deferred calls run only when the function returns, so a loop that opens 1,000 files with
defer f.Close()keeps them all open until the end. Move the loop body into its own function. - Forgetting
...when passing a slice to a variadic function.total(cart)does not compile; writetotal(cart...).
Key points
func name(a, b int) (int, error)declares parameters and results; results can be several values.- Return
(value, error)and checkif err != nilimmediately after the call. - Named results document what is returned; keep bare
returnfor short functions. ...Tmakes a variadic parameter; pass a slice withslice....- Functions are values; closures capture variables from the enclosing function.
deferruns calls in reverse order when the function returns, with arguments fixed at thedeferline.
Exercise
Write a function cartSummary(prices ...int) (count, total int, err error) for a grocery app in Rohtak. It returns the number of items and the total, but returns an error if any price is negative. Call it once with 120, 45, 300 and once with 80, -10, printing either the summary or the error.
Show solution
package main
import "fmt"
func cartSummary(prices ...int) (count, total int, err error) {
for _, p := range prices {
if p < 0 {
return 0, 0, fmt.Errorf("invalid price %d", p)
}
total += p
}
return len(prices), total, nil
}
func main() {
if n, t, err := cartSummary(120, 45, 300); err != nil {
fmt.Println("Error:", err)
} else {
fmt.Println("Items:", n, "Total:", t)
}
if n, t, err := cartSummary(80, -10); err != nil {
fmt.Println("Error:", err)
} else {
fmt.Println("Items:", n, "Total:", t)
}
}Items: 3 Total: 465 Error: invalid price -10
The function returns three values: two named counts and an error. The if short statement keeps n, t and err local to each check.
Quick Quiz
1. How does a Go function usually report that something went wrong?
Go functions return an error (nil on success) alongside their normal results.
2. What does defer do?
Deferred calls run as the function exits, in last-in, first-out order.
3. How do you pass the slice nums to func sum(n ...int) int?
Adding ... after the slice spreads it into the variadic parameter.
4. What is a closure?
A closure keeps access to variables of the enclosing function, like the counter n in tokenMaker.
Learn Go with a mentor
Free lessons get you started. Our Programming & Software Development training adds real projects and code reviews from working developers.
- Project-based learning
- Mentor code reviews
- Free counselling call
