Functions
Functions let you name a piece of logic, give it inputs, and reuse it. Go functions are declared with func, have typed parameters and results, and are values in their own right that can be stored and passed around. This lesson covers declaring and calling functions, parameters, return values, how arguments are passed, and how to organise code into small functions.
Declaring and calling
package main import "fmt" func greet(name string) { fmt.Println("Hello,", name) } func area(width, height float64) float64 { return width * height } func main() { greet("Ana") a := area(3, 4.5) fmt.Println("area:", a) fmt.Println("double:", area(2, 2)*2) }
Hello, Ana area: 13.5 double: 8
The parts of a declaration are: func, the name, the parameter list with each parameter's type after its name, and the result type (if any) before the opening brace. Consecutive parameters of the same type can share it: width, height float64.
Return values
A function with a result type must end every path with a return. The compiler checks this, so you can't accidentally fall off the end:
package main func sign(n int) string { if n > 0 { return "positive" } else if n < 0 { return "negative" } } func main() {}
./main.go:9:1: missing return
package main import "fmt" func sign(n int) string { if n > 0 { return "positive" } if n < 0 { return "negative" } return "zero" } func main() { fmt.Println(sign(5), sign(-2), sign(0)) }
positive negative zero
A function can return several values at once — Go uses this constantly for results plus errors. That's the subject of the next lesson.
Arguments are copied
Go passes every argument by value: the function gets its own copy. Changing a parameter doesn't affect the caller's variable:
package main import "fmt" func reset(n int) { n = 0 } func main() { count := 42 reset(count) fmt.Println(count) }
42
Some types are themselves small handles to shared data — slices, maps, channels and pointers. Copying the handle still lets the function change the shared data (as you saw with slices and maps). To let a function change a plain variable, pass a pointer to it (lesson 37).
Order doesn't matter
Package-level functions can be declared in any order and in any file of the package. There are no forward declarations or header files. Go also has no default parameter values and no function overloading — each function name in a package means exactly one function. When you need options, use separate functions with descriptive names, a variadic parameter (lesson 32) or an options struct (lesson 36).
Small, focused functions
Good functions do one thing and are named for it. Breaking a job into steps makes each step easy to read and test:
package main import ( "fmt" "strings" ) func normalize(email string) string { return strings.ToLower(strings.TrimSpace(email)) } func isValid(email string) bool { at := strings.Index(email, "@") return at > 0 && strings.Contains(email[at:], ".") } func domain(email string) string { _, d, _ := strings.Cut(email, "@") return d } func main() { for _, raw := range []string{" Ana@Example.COM ", "bad-address", "ben@mail.org"} { e := normalize(raw) if !isValid(e) { fmt.Printf("%q is not valid\n", raw) continue } fmt.Println(e, "->", domain(e)) } }
ana@example.com -> example.com "bad-address" is not valid ben@mail.org -> mail.org
Functions are values
A function can be stored in a variable, passed as an argument and returned from another function. Its type is written like its signature, such as func(int) int:
package main import "fmt" func double(n int) int { return n * 2 } func square(n int) int { return n * n } func applyAll(nums []int, f func(int) int) []int { out := make([]int, len(nums)) for i, n := range nums { out[i] = f(n) } return out } func main() { nums := []int{1, 2, 3, 4} fmt.Println(applyAll(nums, double)) fmt.Println(applyAll(nums, square)) op := square fmt.Printf("%d %T\n", op(9), op) }
[2 4 6 8] [1 4 9 16] 81 func(int) int
Lesson 33 goes further, with anonymous functions and closures.
The main and init functions
main in package main is where a program starts. A package can also have one or more init functions, which run automatically before main — used occasionally to set up package-level state. Neither takes arguments or returns values:
package main import "fmt" var greeting string func init() { greeting = "ready" fmt.Println("init runs first") } func main() { fmt.Println("main runs next:", greeting) }
init runs first main runs next: ready
Use init sparingly. Hidden setup makes code harder to follow and test; explicit initialisation in main is usually clearer.
Try it yourself
Write a function that converts a temperature from Celsius to Fahrenheit and use it to print a small conversion table. Then add the reverse function.
-10.0°C = 14.0°F 0.0°C = 32.0°F 10.0°C = 50.0°F 20.0°C = 68.0°F 30.0°C = 86.0°F 40.0°C = 104.0°F