This page is a structured tour of functions in Go for readers who already know another language and want the full picture in one place: syntax, naming, how data goes in and out, value versus pointer semantics, methods, function values, closures, recursion, and defer. Official language material lives in A Tour of Go: Functions, the language specification, and Effective Go — Functions. Deeper follow-ups on this site include multiple return values, errors, variadic functions, the defer keyword, functions versus methods, goroutine return patterns, and two-parameter typing.
Every
mainexample below was run with Go 1.24 on Linux unless noted.
Golang function overview for beginners
A function groups statements, gives them a name, and defines what goes in (parameters) and what comes out (return values). Benefits you use from day one:
- Reuse logic without copy-paste.
- Test units of behavior in isolation.
- Express APIs: small exported functions and methods are the main way Go libraries compose.
The rest of this article follows a typical learning path: syntax and naming, declaring and calling, returns, argument passing, variadic and composite types, methods, anonymous functions and closures, higher-order patterns, recursion, then defer.
Golang function syntax
The general form of a package-level function is:
func name(parameterList) resultList {
// body
}resultList is either a single type (int) or a parenthesized list ((string, int, error)). The caller invokes name(arguments). Go does not use parentheses around the control expression in if / for / switch, but the parameter and result lists in a func always use parentheses as shown in the spec.
package main
import "fmt"
func sayHello() {
fmt.Println("Hello, world!")
}
func greet(name string, age int) string {
return fmt.Sprintf("Hello %s, you are %d years old.", name, age)
}
func main() {
sayHello()
fmt.Println(greet("John Doe", 32))
}Running the program prints Hello, world! and Hello John Doe, you are 32 years old.
main with signature func main() is the entry point for command-line programs in package main.
Naming convention to declare a golang function
Go uses letter case for export: names starting with an upper-case letter (Parse, HTTPClient) are visible outside the package; lower-case names (parse, internal) are package-private. That rule applies to functions, methods, struct fields, and type names. Package names stay short, lower-case, single word when possible (Effective Go — Names). For getters, the Go style is Owner() rather than GetOwner() when the meaning is still clear.
Declaring and calling functions
You declare a function once at package level (or as a nested function literal, covered later). You call it from the same package with name(args) or from another import path as otherpkg.Name(args) when exported.
Functions in the same file can call each other regardless of declaration order. There is no separate prototype syntax: the compiler reads the full file.
Returning data in a golang function
Returning a single value
Use return followed by an expression that matches the declared result type.
package main
import "fmt"
func square(x int) int {
return x * x
}
func main() {
fmt.Println(square(7))
}You should see 49.
Returning multiple values
Parenthesize two or more result types. Callers often use short variable declaration a, b := f().
package main
import "fmt"
func getUser() (string, int) {
return "JohnDoe", 34
}
func main() {
name, age := getUser()
fmt.Println("Username:", name, "Age:", age)
onlyName, _ := getUser()
fmt.Println("ignore age:", onlyName)
}See golang return multiple values for named results, naked return, and ignoring values with _.
Returning an error
The idiomatic pattern for fallible work is (T, error): compute a value or return a zero value plus a non-nil error.
package main
import (
"errors"
"fmt"
"strconv"
)
func parsePositive(s string) (int, error) {
n, err := strconv.Atoi(s)
if err != nil {
return 0, fmt.Errorf("parse: %w", err)
}
if n <= 0 {
return 0, errors.New("must be positive")
}
return n, nil
}
func main() {
fmt.Println(parsePositive("42"))
fmt.Println(parsePositive("-1"))
}You should see (42 <nil>) then (0 must be positive) (or similar wrapped parse error for non-numeric input). Always check err before using other results. More patterns live under golang return error.
Returning a pointer
Returning *T lets callers share one mutable value or avoid copying a large struct. It is safe to return the address of a local variable: the compiler allocates it on the heap when it must escape (see go build -gcflags=-m if you are curious).
package main
import "fmt"
type Point struct{ X, Y int }
func NewPoint(x, y int) *Point {
return &Point{X: x, Y: y}
}
func main() {
p := NewPoint(3, 4)
fmt.Println(p.X + p.Y)
}You should see 7.
Named result parameters
You may name results and use bare return inside the body. This is optional sugar; use it when names improve documentation, not when they obscure control flow.
package main
import (
"errors"
"fmt"
)
func divide(a, b float64) (q float64, err error) {
if b == 0 {
err = errors.New("zero divisor")
return
}
q = a / b
return
}
func main() {
fmt.Println(divide(10, 2))
fmt.Println(divide(10, 0))
}You should see 5 <nil> then 0 zero divisor.
Passing arguments to a golang function
Go passes every argument by value: the function receives a copy of what the caller passed. For pointer types, the copied value is the address, so both caller and callee share the same pointed-to memory.
Arguments passed by value
Primitives (int, float64, string, small structs) are independent copies; mutating a parameter does not change the caller’s variable.
package main
import "fmt"
func byValue(name string) {
name = "changed"
}
func main() {
s := "original"
byValue(s)
fmt.Println("after byValue:", s)
}You should see after byValue: original.
Arguments passed by reference (pointers)
Pass &x when the callee should update x in the caller, or when copying a large struct is wasteful.
package main
import "fmt"
func byPointer(name *string) {
*name = "changed"
}
func main() {
s := "original"
byPointer(&s)
fmt.Println("after byPointer:", s)
}You should see after byPointer: changed.
Struct, map, slice, and channel parameters
Composite types still follow pass-by-value of the value’s header or descriptor.
Structs: copying the whole struct means field writes on the parameter do not affect the caller unless you pass *struct or return the updated struct.
package main
import "fmt"
type counter struct{ n int }
func bumpCopy(c counter) { c.n++ }
func bumpPtr(c *counter) { c.n++ }
func main() {
x := counter{1}
bumpCopy(x)
fmt.Println("after copy:", x.n)
bumpPtr(&x)
fmt.Println("after ptr:", x.n)
}You should see after copy: 1 then after ptr: 2.
Maps and channels: the callee receives a copy of the map or channel descriptor, but it still refers to the same runtime map or channel, so mutations are visible to the caller.
package main
import "fmt"
func setKey(m map[string]int) { m["x"] = 42 }
func main() {
m := map[string]int{}
setKey(m)
fmt.Println(m["x"])
}You should see 42.
Slices: the slice header (pointer, length, capacity) is copied; indexing or mutating elements through that header affects the shared backing array. Reassigning the slice variable inside the callee does not replace the caller’s slice header unless you pass *[]T or return the new slice.
Variadic input parameters and slices
A variadic parameter uses ...T and must be the last parameter; inside the function it behaves as []T. Callers can pass a, b, c or slice....
package main
import "fmt"
func printAll(items ...string) {
for _, item := range items {
fmt.Println(item)
}
}
func main() {
printAll("Go", "Docker", "Linux")
}For unpacking slices, mixing with fixed parameters, and pitfalls like nil versus empty variadic arguments, see variadic functions in Go.
Go methods
A method declares a receiver before the function name. Receivers may be value T or pointer *T.
package main
import "fmt"
type User struct {
username string
password string
}
func (u *User) setPassword(p string) {
u.password = p
}
func (u User) summary() string {
return fmt.Sprintf("user=%s", u.username)
}
func main() {
u := User{username: "jdoe", password: "old"}
fmt.Println(u.summary())
u.setPassword("new")
fmt.Println("password:", u.password)
}Pointer receivers are required when the method mutates the receiver or when you want to avoid copying large structs. Value receivers are fine for small immutable views. More trade-offs sit in functions versus methods and A Tour of Go: Methods.
Functions versus methods
| Idea | Package-level function | Method |
|---|---|---|
| Syntax | func Name(...) |
func (r T) Name(...) |
| Call site | Name(args) |
value.Name(args) |
| Typical use | Algorithms that do not need a receiver state | Behavior tied to a type’s fields or invariants |
Use functions for pure utilities; use methods when the first argument would always be the same conceptual “subject” of the operation.
Anonymous functions
A function literal has no name at package level; you assign it, pass it, or invoke it immediately.
package main
import "fmt"
func main() {
double := func(x int) int { return x * 2 }
fmt.Println(double(21))
func(msg string) {
fmt.Println(msg)
}("inline IIFE")
}You should see 42 then inline IIFE.
Go closure function
A closure is a function value that references variables from an enclosing lexical scope. Those variables stay alive as long as the closure does.
package main
import "fmt"
func makeAdder(base int) func(int) int {
return func(x int) int {
return base + x
}
}
func main() {
addFive := makeAdder(5)
fmt.Println(addFive(10))
}You should see 15.
When a closure is created inside a loop, capture the loop variable explicitly if you need per-iteration binding (historically i := i inside the loop body; Go 1.22+ changed for loop variable semantics so each iteration gets its own i for range and three-clause for, but explicit capture still reads clearly in complex loops).
package main
import "fmt"
func main() {
var fs []func()
for i := 0; i < 3; i++ {
i := i
fs = append(fs, func() { fmt.Println(i) })
}
for _, f := range fs {
f()
}
}You should see 0, 1, and 2 on separate lines.
Passing functions as parameters
Declare a parameter with the function type you need; callers pass nil only if the API allows it (rare).
package main
import (
"fmt"
"sort"
)
func main() {
nums := []int{3, 1, 4}
sort.Slice(nums, func(i, j int) bool {
return nums[i] < nums[j]
})
fmt.Println(nums)
}You should see [1 3 4]. The net/http package uses related patterns (HandlerFunc) to adapt functions into interfaces.
Returning functions from functions
Higher-order functions return customized behavior—factories, decorators, or scoped configuration.
package main
import "fmt"
func withPrefix(prefix string) func(string) string {
return func(s string) string {
return prefix + ": " + s
}
}
func main() {
log := withPrefix("app")
fmt.Println(log("started"))
}You should see app: started.
Recursive functions
Go supports recursion like C; each call allocates a new stack frame, so deep recursion can overflow the stack for pathological inputs. Always include a base case.
package main
import "fmt"
func fact(n int) int {
if n <= 1 {
return 1
}
return n * fact(n-1)
}
func main() {
fmt.Println(fact(5))
}You should see 120.
Using the defer keyword
defer schedules a call to run when the surrounding function returns, after any return values are evaluated but before control returns to the caller. Deferred calls run in last-in-first-out order.
package main
import "fmt"
func main() {
defer fmt.Println(1)
defer fmt.Println(2)
fmt.Println(3)
}You should see 3, then 2, then 1 on separate lines—the deferred prints run after the body finishes, newest defer first.
Typical production uses: defer f.Close() after a successful os.Open, defer mu.Unlock() after Lock, or pairing defer with recover in server handlers (use sparingly). A full treatment lives in the defer keyword guide and the spec: defer statements.
Summary
Go functions are declared with func, optional receiver, name, parameters, results, and body. Data comes back as single or multiple return values; errors are ordinary return values. Arguments are always passed by value, so pointers, maps, channels, and slice headers explain shared mutation. Variadic parameters are summarized here and expanded in the variadic article. Methods attach behavior to types; function literals and closures capture environment; higher-order APIs accept and return functions. Recursion needs a base case; defer orders cleanup on return. Together these topics match how the language and libraries express behavior—see the official links below when you need normative wording.
References
- A Tour of Go — Functions
- A Tour of Go — Methods
- Effective Go — Functions
- Go language specification — Function declarations
- Go language specification — Function calls
- Go language specification — Defer statements
Frequently Asked Questions
1. What is golang function syntax?
func, an optional receiver for methods, the name (optional for function literals), the parameter list in parentheses, an optional result list, and a body in braces; for example func(name string, age int) (string, error).2. How do golang multiple types appear in one function?
(int, error); callers assign with a, b := f() or ignore with _.3. How do I golang pass function as parameter?
func(int, int) bool and pass a matching function value or literal; the standard library uses this pattern in sort.Slice, HTTP handlers, and many callbacks.4. What is a golang function type?
func(int) int; values of that type are callable and can be stored in variables or passed like any other value.5. What is the difference between a function and a method in Go?
func (r *T) Name(), and is called on a value of that type with dot syntax; a plain package-level function has no receiver.6. If maps are passed by value, why does a function change the caller map?
7. What does defer do in Go?
Close on successful Open calls. See the defer guide linked from this article.8. When should I use a pointer receiver on a method?
*T when the method must mutate the receiver, when the struct is large and copying is costly, or when you need consistency if some methods mutate; use value T for small immutable data or when you must protect copies.
