Functions names are also variables, if declared outside of the block they are by default package scoped variables.
The main function has no input parameters or return values.
Just as in many other languages, functions in Go are values. The type of a function is built out of the keyword func and the types of the parameters and return values. This combination is called the signature of the function. Any function that has the exact same number and types of parameters and return values meets the type signature.
The default zero value for a function variable is nil
Call by Value, Call by Reference
Important
In Go, everything is passed by value.
But if the value includes a pointer, you can still modify what it points to
Go is a call by value language.
“Pass by value” → how arguments are given to a function.
“Call by value” → what happens during the function call.
func modifyFails(i int, s string, p person)
i, s, and p are local copies of the variables from main.
Any modification affects only the copies, not the originals.
In Java or Python, passing an object means passing a reference — so changing its internal state does affect the original.
In Go, a struct is copied when passed, so changing its fields doesn’t affect the original.
To modify the original variables, pass their addresses:
func modifyWorks(i *int, s *string, p *person) { *i = 99 *s = "Changed" p.age = 100 // Shorter: no *p.age, Go handles it}func main() { p := person{} i := 2 s := "Hello" modifyWorks(&i, &s, &p) fmt.Println(i, s, p)}
So when the function changes the contents that the pointer points to, you see the changes outside the function — even though the slice/map itself is passed by value.
func modify(s []int) { s[0] = 100 // ✅ Modifies original array s = append(s, 200) // ❌ Only changes local copy of s}func main() { nums := []int{1, 2, 3} modify(nums) fmt.Println(nums) // [100 2 3] — not [100 2 3 200]}
So when you pass a map:
The map header is copied (passed by value).
But it points to the same underlying data.
So changes to keys/values are visible outside.
func modify(m map[string]int) { m["x"] = 42 // âś… modifies the original map}func main() { m := make(map[string]int) modify(m) fmt.Println(m) // map[x:42]}
Named and Optional Parameters
Go doesn't have name or optional input parameters.
# Named Parameters in Pythondef connect(host, port): ...connect(port=5432, host="localhost")# Optional Parameters / Default Argumentsdef greet(name="Guest"): print("Hello,", name)greet() # Hello, Guestgreet("Alice") # Hello, Alice
Simulate
type Options struct { Host string Port int}func connect(opts Options) { fmt.Println("Connecting to", opts.Host, "on port", opts.Port)}// Call it like this:connect(Options{Host: "localhost", Port: 5432})
Variadic Input Parameters (Function)
A variadic function in Go is a function that can accept a variable number of arguments of a specified type.
Warning
A variadic parameter must always be the last parameter in the function signature. You cannot have a regular parameter after a variadic one.
func addTo(base int, vals ...int) []int { out := make([]int, 0, len(vals)) for _, v := range vals { out = append(out, base+v) } return out}func main() { fmt.Println(addTo(3)) fmt.Println(addTo(3, 2)) fmt.Println(addTo(3, 2, 4, 6, 8)) a := []int{4, 3} fmt.Println(addTo(3, a...)) fmt.Println(addTo(3, []int{1, 2, 3, 4, 5}...))}// Output[][5][5 7 9 11][7 6][4 5 6 7 8]
The Spread Operator (or Variadic Functions) in Golang
Return Values
Multiple Return Values
Use _ whenever you don’t need to read a value that’s returned by a function. Surprisingly, Go does let you implicitly ignore all of the return values for a function.
Named Return Values
You must surround named return values with parentheses, even if there is only a single return value.
Named return values are initialized to their zero values when created. This means that you can return them before any explicit use or assignment.
If you want to name only some of the return values, you can do so by using _ as the name for any return values you want to remain nameless.
Inner functions are anonymous; they don’t have a name. You declare an anonymous function with the keyword func immediately followed by the input parameters.
func main() { f := func(j int) { fmt.Println("printing", j, "from inside of an anonymous function") } for i := 0; i < 5; i++ { f(i) }}
To create an anonymous function, we use a function literal.
package mainimport "fmt"func main() { // an anonymous function func() { // the instructions of the anonymous function fmt.Println("I am anonymous function") }() }
Since functions are values and you can specify the type of a function using its parameter and return types, you can pass functions as parameters into functions.
Functions with the same signature share the same function type.
Function type defines the signature of a function: parameter types and return types.
package mainimport "fmt"type MathOperation func(int, int) intfunc add(a, b int) int { return a + b }func subtract(a, b int) int { return a - b }func main() { // Variables can hold functions matching the type: var op MathOperation op = add fmt.Println("Add:", op(5, 3)) // Output: Add: 8 op = subtract fmt.Println("Subtract:", op(5, 3)) // Output: Subtract: 2}
Functions as parameters or return values:
func apply(f MyFunc, i int, s string) bool { return f(i, s)}res := apply(func(i int, s string) bool { return s == "go" }, 2, "go")
Callback Functions
A callback function is a function that is passed as an argument to another function, which then executes the callback function at a certain point.
func greet(name string, callback func(string)) { fmt.Printf("Hello, %s!\n", name) callback(name) // invoke the callback with name}func main() { greet("Alice", func(n string) { fmt.Printf("Nice to meet you, %s!\n", n) })}
Callback in Struct fields / Function as a struct field
type Button struct { OnClick func()}func main() { btn := Button{ OnClick: func() { fmt.Println("Button clicked!") }, } // Later when clicked: if btn.OnClick != nil { btn.OnClick() }}
An anonymous function defined into another function F can use elements that are not defined in its scope but in the scope of F.
Just as with any inner scope, you can shadow a variable inside a closure. If you change the code to:
func main() { a := 20 f := func() { fmt.Println(a) a := 30 fmt.Println(a) } f() fmt.Println(a)}// Output203020
Returning Functions from Functions / Higher-Order Functions
If you spend any time with programmers who use functional programming languages like Haskell, you might hear the term higher-order functions. That’s a fancy way to say that a function has a function for an input parameter or a return value.
While Go is not a purely functional language, it allows functions to be treated as first-class citizens. This means you can:
Assign functions to variables.
Pass functions as arguments.
Return functions from other functions.
higher-order functions are functions that either:
Take other functions as arguments.
Return functions as results.
Deferred Functions
A deferred function is executed after the surrounding function completes, typically for cleanup tasks (e.g., closing files or releasing resources).
func main() { defer fmt.Println("This will be printed last") fmt.Println("This will be printed first")}