Basic Function Syntax
Understand how to declare and call functions in Go - covering parameters, return types, the function body, and the distinction between named and unnamed result parameters
Functions are the building blocks of any Go program. Every executable Go program has at least one function: main. But beyond that entry point, functions let you organize logic into reusable, named blocks of code that take inputs, perform actions, and produce outputs. In Go, a function is declared with the func keyword, and its structure is straightforward: a name, an optional parameter list, an optional return type, and a body enclosed in braces.
The Anatomy of a Function Declaration
A function declaration in Go has four components that appear in a fixed order. The shortest possible function has no parameters and returns nothing:
func greet() {
fmt.Println("Hello")
}
The general form is:
func functionName(parameterList) returnType {
// body
}
Both the parameter list and the return type are optional. A function can take zero or more parameters and return zero or more values. If a function returns nothing, the return type is omitted entirely.
Exported vs Unexported Functions:
The first letter of the function name controls its visibility across packages. An uppercase first letter makes the function exported and accessible from other packages (like fmt.Println). A lowercase first letter keeps the function unexported, visible only within its own package. This is Go's equivalent of public and private.
Parameters
Parameters are declared between parentheses after the function name. In Go, each parameter is written as a name followed by its type:
func add(x int, y int) int {
return x + y
}
Placing the type after the name may feel unfamiliar if you come from languages like C or Java, but it reads naturally: "x is an int, y is an int."
When consecutive parameters share the same type, you can shorten the declaration by stating the type only once at the end of the group:
func add(x, y int) int {
return x + y
}
This shorthand is common in idiomatic Go. It works for any number of parameters with the same type in a row, as long as they appear contiguously.
Pass by Value, Not Reference:
All arguments in Go are passed by value. That means the function receives a copy of the data, not the original variable. Modifying the parameter inside the function does not affect the caller's variable. This is the single most misunderstood aspect of Go function calls.
func increment(n int) {
n++
fmt.Println("Inside function:", n)
}
func main() {
x := 5
increment(x)
fmt.Println("After call:", x) // still 5
}
If you need to modify the original value, you must pass a pointer. Pointers are covered in later chapters, but for basic function syntax, remember: the function works on a copy.
Return Types
A function's return type is written after the parameter list. If the function returns a single value, the type appears directly:
func double(n int) int {
return n * 2
}
If a function returns multiple values, the return types are enclosed in parentheses and separated by commas. Multiple return values are a distinguishing feature of Go, often used to return a result alongside an error:
func divide(a, b float64) (float64, error) {
if b == 0 {
return 0, errors.New("division by zero")
}
return a / b, nil
}
For basic function syntax, the key point is that a function can return zero, one, or multiple values, and the return type signature must match what the function actually returns. The full treatment of multiple return values, including error handling patterns, appears in the next section of this chapter.
Missing Return Statement:
If a function declares a return type, the Go compiler requires every possible execution path to end with a return statement that supplies a value of the correct type. Omitting a return—or failing to return on some branch—is a compile-time error, not a runtime bug.
Named Result Parameters
Go allows you to name the result parameters in the function signature. The names act as variables that are initialized to their zero value at the start of the function and are automatically returned when you use a bare return statement:
func rectangleProps(length, width float64) (area, perimeter float64) {
area = length * width
perimeter = 2 * (length + width)
return // returns area and perimeter
}
This is what the existing notes refer to as "named vs unnamed function declarations." In an unnamed declaration, you return explicit values. In a named declaration, you assign to the named result variables and then write return without any values. Both forms are valid, but named result parameters can improve readability in short functions, especially when the returned values have clear semantic meaning. The detailed rules and best practices around named result parameters are covered in their own section later in this chapter.
Bare Return Works:
If you compile and run the rectangleProps function, the bare return will correctly return the current values of area and perimeter. The compiler keeps track of the named result variables and guarantees they are returned.
The Function Body
The function body is the block of code between { and }. In Go, the opening brace must appear on the same line as the function signature—this is enforced by the compiler and by gofmt. Within the body, you write the statements that implement the function's logic. If the function declares a return type, the body must include at least one return statement that satisfies it.
Here is a complete, self-contained program that uses a basic function:
package main
import "fmt"
// calculateTotal returns the total cost given price and quantity.
func calculateTotal(price, quantity int) int {
total := price * quantity
return total
}
func main() {
price := 120
quantity := 3
total := calculateTotal(price, quantity)
fmt.Println("Total price is", total)
}
Running this program prints:
Total price is 360
The function calculateTotal takes two int parameters, computes their product, and returns it. In main, the returned value is captured in the variable total. This is the most common pattern: a function that receives data, processes it, and hands back a result.
Calling Functions
To execute a function, you write its name followed by parentheses containing the arguments. The arguments must match the parameter types in number and order. If the function returns values, you capture them with assignment:
result := add(5, 7)
When a function returns multiple values, you must assign them to a matching number of variables on the left side:
q, err := divide(10.0, 3.0)
If you only need some of the returned values, use the blank identifier _ to discard the rest:
area, _ := rectangleProps(10.8, 5.6) // perimeter is discarded
The blank identifier is a deliberate way to signal that a return value is intentionally ignored. Leaving a returned value completely unused is a compile-time error; _ satisfies the compiler while making the discarding explicit.
Common Mistakes and Misconceptions
Beginners often hit a few specific stumbling blocks with basic function syntax.
Forgetting the return type on a non-void function. If your function produces a value, you must declare its type after the parameter list. Omitting it makes the compiler think the function returns nothing, and the return statement inside will cause an error.
Expecting parameter changes to persist outside the function. As shown earlier, Go always passes by value. Changing a parameter inside the function does not alter the original variable. This trips up developers who are used to pass-by-reference semantics.
Mixing up parameter order when calling a function. Go does not support named argument passing at the call site. You must provide arguments in the exact order the function declares them. If a function expects (name string, age int), calling myFunc(30, "Alice") will produce a compile error, but with different types it's easier to catch; with multiple parameters of the same type, ordering mistakes can slip through and produce wrong results.
Using the blank identifier when you shouldn't. Discarding an error return value with _ is a well-known anti-pattern in production Go code. While _ is a valid tool, throwing away an error without handling it can hide bugs. For basic syntax, understand that _ exists and how to use it; later sections will cover error handling in depth.
Summary
Function syntax in Go is deliberately small: func, a name, parameters with their types after the names, an optional return type, and a body in braces. The language enforces a consistent layout through compiler rules and gofmt, so every function you encounter will follow the same structure. Two design choices—pass-by-value and the blank identifier—shape how you reason about data flow and return value usage from the very first function you write.