A function names a piece of work and makes its inputs and outputs explicit. A useful function lets its caller understand the contract without reading every line inside. In Go, that contract includes parameter types and result types. This lesson builds a few small functions and follows their execution order, including cleanup scheduled with defer.
Use a fresh module and save the following complete program as main.go. Read main first to see the tasks, then inspect each function that it calls.
Parameters and multiple results
package main
import "fmt"
func divide(total, groups int) (int, bool) {
if groups <= 0 {
return 0, false
}
return total / groups, true
}
func sum(values ...int) int {
total := 0
for _, value := range values {
total += value
}
return total
}
func counter() func() int {
value := 0
return func() int {
value++
return value
}
}
func trace() {
label := "start"
defer fmt.Println("saved:", label)
defer func() {
fmt.Println("current:", label)
}()
label = "finish"
fmt.Println("body:", label)
}
func main() {
quotient, ok := divide(10, 3)
fmt.Println("division:", quotient, ok)
fmt.Println("sum:", sum(2, 3, 4))
next := counter()
fmt.Println("counter:", next(), next())
trace()
}
Run go run .. Expected output:
division: 3 true
sum: 9
counter: 1 2
body: finish
current: finish
saved: start
divide(total, groups int) gives both parameters the type int. Its two results have types int and bool. Here the boolean means the divisor was accepted; integer division can still leave a remainder. Returning a boolean works for a simple yes/no contract. Later, errors will carry an explanation when an operation fails.
Go passes arguments by value: each parameter receives a copy of the argument value. For integers that means changing the parameter does not change the caller's variable. Some values, such as slices and pointers, contain access to shared data; copying those values does not copy all the underlying data. Keep this distinction in mind for the next lessons. The functions tour covers the declaration syntax.
Variadic functions and function values
values ...int is a variadic parameter: the caller can supply zero or more integers. Inside sum, values is a slice. A variadic parameter must be last. If you already have a slice named numbers, call sum(numbers...) to pass its elements as arguments.
Functions are values too. counter returns a function rather than an integer. The returned function is a closure, a function that can use variables from its surrounding scope. Each call to next changes the same captured value. Calling counter again creates an independent counter. This is useful for callbacks and small pieces of state, but captured mutable state still needs synchronization if accessed concurrently. See the closure introduction.
Follow the deferred calls
defer schedules a call for when the surrounding function returns. Deferred calls execute in reverse registration order. In trace, the anonymous function is registered second, so it runs first during the return.
Arguments to a deferred call are evaluated when the defer statement executes. The direct Println therefore saves "start". The closure reads the captured variable when it runs and sees "finish". This difference explains the last two lines; it is not a scheduling race. The official defer article describes these rules.
The common resource pattern is: acquire something, check the acquisition error, then immediately defer its cleanup. For example, defer closing an opened file only after opening succeeds. Cleanup errors can matter, especially for writes; do not assume every Close result is harmless.
Pitfalls in function design
Deferred calls run at function return, not at the end of each loop iteration. Deferring many file closes inside one long loop can keep too many files open. Put one iteration's resource work in a helper function when it needs a shorter lifetime.
Prefer explicit returns in longer functions. Named result variables are legal, but a bare return can make readers search for the result's last assignment. Also keep side effects visible: a function that calculates a total is easier to reuse if it returns the total and lets its caller decide whether to print it.
Exercise: track an independent counter
Change counter into counterFrom(start int) func() int. The first call should return start + 1. Create counters starting at zero and ten, then alternate calls. Add a call to sum() and predict its result before running.
Solution guidance
Initialize the captured value from start and leave the returned function's increment unchanged. Each invocation of counterFrom owns a separate captured variable. Calling the first counter, then the second, then the first again should produce 1, 11, 2. sum() returns zero because the loop has no elements and total starts at its zero value.
Previous: Variables and Control Flow. Next: Collections and Strings.

