A Comprehensive Guide to Functions in Go

Functions in Go are fundamental building blocks for organizing code, performing tasks, and enabling reusability. They are defined using the func keyword and can accept parameters, return values, or both.

Defining Functions

A function declaration specifies its name, parameters, and return types. Go requires atleast one main function as the entry point.

// Function with no parameters and no return value
func sayHello() {
    fmt.Println("Hello, Go!")
}

// Function with parameters and no return value
func greet(name string) {
    fmt.Printf("Hello, %s!\n", name)
}

// Function with parameters and a return value
func add(x, y int) int {
    return x + y
}

// Function with multiple return values
func divide(a, b int) (int, int) {
    quotient := a / b
    remainder := a % b
    return quotient, remainder
}

func main() {
    sayHello()
    greet("Charlie")
    sum := add(5, 3)
    q, r := divide(10, 3)
    fmt.Println(sum, q, r)
}

Variadic Functions

Variadic functions accept a variable number of arguments of the same type. The arguments are collected into a slice within the function. The variadic parameter must be the last one in the parameter list, and a function can have at most one such parameter.

func calculateSum(nums ...int) int {
    total := 0
    for _, val := range nums {
        total += val
    }
    return total
}

func main() {
    result := calculateSum(1, 2, 3, 4, 5)
    fmt.Println(result) // Output: 15
}

Parameter Passing: Value vs. Reference

Parameters are passed by value by default, meaning a copy of the data is provided to the function. However, for reference types (e.g., slices, maps, channels), modifications inside the function affect the original data because they share the underlying data structure.

// Value type (array) - original unchanged
func modifyArray(arr [4]int) {
    arr[0] = 100
    fmt.Println("Inside function:", arr)
}

// Reference type (slice) - original modified
func modifySlice(sl []int) {
    sl[0] = 100
    fmt.Println("Inside function:", sl)
}

func main() {
    myArray := [4]int{1, 2, 3, 4}
    modifyArray(myArray)
    fmt.Println("After function:", myArray) // Output: [1 2 3 4]

    mySlice := []int{1, 2, 3, 4}
    modifySlice(mySlice)
    fmt.Println("After function:", mySlice) // Output: [100 2 3 4]
}

Variable Scope

Variables declared inside a funcsion (local variables) are only accessible within that function. Variables declared outside any function (global variables) are accessible throughout the package.

package main

import "fmt"

var globalVar = 100 // Global variable

func main() {
    localVar := 50 // Local variable
    if true {
        innerVar := 10 // Block-scoped variable
        fmt.Println(innerVar)
    }
    // fmt.Println(innerVar) // Error: undefined
    fmt.Println(localVar)
    fmt.Println(globalVar)
}

Recursive Functions

A recursive function calls itself and must have a terminating condition to avoid infinite loops.

func factorial(n int) int {
    if n == 1 {
        return 1
    }
    return n * factorial(n-1)
}

func main() {
    result := factorial(5)
    fmt.Println(result) // Output: 120
}

Deferred Functions

The defer statement schedules a function call to be executed after the surrounding function returns. Deferred calls are executed in LIFO order (last deferred, first executed).

func main() {
    defer fmt.Println("first defer")
    defer fmt.Println("second defer")
    defer fmt.Println("third defer")
    fmt.Println("Main body")
}
// Output:
// Main body
// third defer
// second defer
// first defer

Functions as Values

Functions are first-class types in Go. They can be assigned to variables, passed as arguments, and returned from other functions.

func add(a, b int) int {
    return a + b
}

func main() {
    var op func(int, int) int
    op = add
    result := op(3, 4)
    fmt.Println(result) // Output: 7
}

Anonymous Functions

Anonymous functions have no name and can be defined inline. They are often used for closures or callbacks.

func main() {
    // Assign to a variable
    multiply := func(a, b int) int {
        return a * b
    }
    fmt.Println(multiply(3, 4))

    // Immediately invoked function expression (IIFE)
    func(msg string) {
        fmt.Println(msg)
    }("Hello from anonymous!")
}

Higher-Order and Callback Functions

A higher-order function takes one or more functions as arguments or returns a function. A callback is a function passed as an argument to another function.

func operate(x, y int, callback func(int, int) int) int {
    return callback(x, y)
}

func main() {
    add := func(a, b int) int { return a + b }
    sub := func(a, b int) int { return a - b }

    resultAdd := operate(10, 5, add)
    resultSub := operate(10, 5, sub)
    fmt.Println(resultAdd, resultSub) // Output: 15 5
}

Closures

A closure is a function that captures variables from its outer scope. Even after the outer function finishes execution, the inner function retains access to those variables.

func counter() func() int {
    count := 0
    return func() int {
        count++
        return count
    }
}

func main() {
    inc := counter()
    fmt.Println(inc()) // 1
    fmt.Println(inc()) // 2

    newInc := counter()
    fmt.Println(newInc()) // 1 (new counter, fresh start)
}

Tags: functions Golang programming go basics

Posted on Tue, 12 May 2026 14:19:26 +0000 by altergothen