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

GO
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)
}
Output
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:

GO
package main

func sign(n int) string {
	if n > 0 {
		return "positive"
	} else if n < 0 {
		return "negative"
	}
}

func main() {}
Compiler error
./main.go:9:1: missing return
GO
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))
}
Output
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:

GO
package main

import "fmt"

func reset(n int) {
	n = 0
}

func main() {
	count := 42
	reset(count)
	fmt.Println(count)
}
Output
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:

GO
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))
	}
}
Output
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:

GO
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)
}
Output
[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:

GO
package main

import "fmt"

var greeting string

func init() {
	greeting = "ready"
	fmt.Println("init runs first")
}

func main() {
	fmt.Println("main runs next:", greeting)
}
Output
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.

Try it yourself · main.go
Edit the code, copy it into main.go in a new folder and run it with go run main.go (Go 1.27 or later). How to install Go
Output
-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
Next: returning several values.