Skip to content

Go Functions

Functions are fundamental building blocks in Go (and most programming languages). They are reusable blocks of code designed to perform a specific task. Every Go program contains at least one function: main(), which serves as the program’s entry point.

Breaking down your code into smaller, well-defined functions improves organization, readability, and maintainability. Each function should ideally have a single, clear purpose.

A function declaration introduces its name, parameters, and return types. A function definition provides the actual code (the function body) that executes when the function is called.

Go’s standard library provides many useful built-in functions. For instance, len() can determine the length of strings, arrays, slices, maps, and channels.

The syntax for defining a function in Go is:

func functionName(parameterList) returnTypeList {
// Body of the function: statements to execute
// ...
return // Optional: returns values if specified in returnTypeList
}

Components of a function definition:

  • func Keyword: Marks the beginning of a function definition.
  • functionName: The name you choose for the function. Follows Go naming conventions (camelCase for unexported, PascalCase for exported).
  • parameterList: A comma-separated list of parameters within parentheses (). Each parameter has a name followed by its type (e.g., name string, age int). If consecutive parameters share the same type, you can omit the type for all but the last one (e.g., x, y int). Functions can have zero parameters ().
  • returnTypeList: Specifies the type(s) of value(s) the function returns. Can be a single type (int), multiple types enclosed in parentheses ((string, error)), or omitted entirely if the function doesn’t return anything.
  • Function Body: The code block enclosed in curly braces {} containing the statements that perform the function’s task.
  • return Statement: Used to return values from the function. The types of the returned values must match the returnTypeList. If the function has no return types, return can be omitted or used alone to exit early.

Example: Finding the Maximum of Two Integers

Section titled “Example: Finding the Maximum of Two Integers”

Here’s a function max that takes two integers and returns the larger one:

// Function definition
// Takes two 'int' parameters (num1, num2)
// Returns a single 'int' value
func max(num1, num2 int) int {
var result int // Declare a local variable
if num1 > num2 {
result = num1
} else {
result = num2
}
return result // Return the calculated maximum value
}

To use a function, you call it by its name, followed by parentheses containing the arguments (actual values) corresponding to the function’s parameters.

When a function is called, the program’s control transfers to the function. The function executes its body, and when it hits a return statement or the closing brace }, control returns to the point where the function was called. If the function returns values, they can be assigned to variables.

package main
import "fmt"
// Function definition (as above)
func max(num1, num2 int) int {
if num1 > num2 {
return num1
}
return num2 // Simplified return
}
func main() {
// Define local variables in main
a := 100
b := 200
var maxValue int
// Call the 'max' function with 'a' and 'b' as arguments
// Assign the returned value to 'maxValue'
maxValue = max(a, b)
fmt.Printf("Max value between %d and %d is: %d\n", a, b, maxValue)
}

Running this program produces:

Max value between 100 and 200 is: 200

A unique feature of Go is that functions can return multiple values. This is often used to return a result and an error status simultaneously.

package main
import "fmt"
// Function returns two string values
func swap(x, y string) (string, string) {
return y, x // Return y first, then x
}
func main() {
firstName := "Alice"
lastName := "Bob"
fmt.Printf("Original: %s %s\n", firstName, lastName)
// Assign the two returned values to two variables
swappedLast, swappedFirst := swap(firstName, lastName)
fmt.Printf("Swapped: %s %s\n", swappedFirst, swappedLast)
}

Output:

Original: Alice Bob
Swapped: Bob Alice

Go allows naming the return values in the function signature. This can improve readability, especially for functions returning multiple values. Named return values are initialized to their zero values. A bare return statement will automatically return the current values of the named return variables.

package main
import "fmt"
// Function with named return values (result int, err error)
func divide(dividend, divisor int) (result int, err error) {
if divisor == 0 {
err = fmt.Errorf("cannot divide by zero") // Assign to named return 'err'
// A bare return here would return the current values (result=0, err=the error)
return
}
result = dividend / divisor // Assign to named return 'result'
// err remains nil (its zero value)
return // Returns current values of result and err
}
func main() {
res1, err1 := divide(10, 2)
if err1 != nil {
fmt.Println("Error:", err1)
} else {
fmt.Println("10 / 2 =", res1)
}
res2, err2 := divide(5, 0)
if err2 != nil {
fmt.Println("Error:", err2)
} else {
fmt.Println("5 / 0 =", res2)
}
}

Output:

10 / 2 = 5
Error: cannot divide by zero

In Go, all function arguments are passed by value. This means the function receives a copy of the argument’s value, not a reference to the original variable.

For basic types (like int, float64, string, bool, structs), modifications made to the parameter inside the function do not affect the original argument variable outside the function.

However, for reference types like slices, maps, and channels, the value being copied is essentially a pointer or header describing the underlying data structure. While the pointer/header itself is copied, it still points to the same underlying data. Therefore, modifications made to the elements of a slice or map inside the function will be visible outside the function. Appending to a slice might change the underlying array, which might or might not be reflected outside, depending on capacity changes (this is a subtle point often covered in slice details).

To explicitly allow a function to modify an original variable (like an int or a struct), you pass a pointer to that variable (pass-by-pointer, which is still technically pass-by-value where the value is the address).

Passing MechanismDescription & Effect
Pass by Value (Default)Function receives a copy. Changes inside don’t affect the original (for basic types/structs). For slices/maps, changes to elements affect the original.
Pass by PointerFunction receives a copy of the memory address (pointer). Changes made via dereferencing (*ptr) affect the original variable.
ConceptDescription
Variadic FunctionsFunctions that can accept a variable number of arguments of the same type.
Anonymous FunctionsFunctions defined without a name, often used immediately or assigned to variables.
ClosuresAnonymous functions that ‘close over’ variables from their surrounding scope, retaining access even after the outer function has finished.
MethodsFunctions associated with a specific type (the receiver). Defined with a receiver argument before the function name.
Deferred Function CallsUsing the defer keyword schedules a function call to be executed just before the surrounding function returns.