Function Basics
Functions in Go are defined with the func keyword. Unlike most languages, Go puts the type after the parameter name. Functions can return multiple values — this is fundamental to Go's error handling pattern.
package main
import "fmt"
// Basic function
func greet(name string) string {
return "Hello, " + name + "!"
}
// Multiple parameters of same type
func add(a, b int) int {
return a + b
}
// Multiple return values
func divide(a, b float64) (float64, error) {
if b == 0 {
return 0, fmt.Errorf("cannot divide by zero")
}
return a / b, nil
}
func main() {
fmt.Println(greet("Gopher"))
fmt.Println(add(3, 4))
result, err := divide(10, 3)
if err != nil {
fmt.Println("Error:", err)
} else {
fmt.Printf("10 / 3 = %.2f\n", result)
}
}
Hello, Gopher! 7 10 / 3 = 3.33
result, err := doSomething() followed by if err != nil is the most common code pattern in Go.Named Return Values
You can name return values in the function signature. They act as pre-declared variables and a bare return returns their current values:
func rectangleProps(length, width float64) (area, perimeter float64) {
area = length * width
perimeter = 2 * (length + width)
return // returns area and perimeter
}
area, perim := rectangleProps(5, 3)
fmt.Printf("Area: %.0f, Perimeter: %.0f\n", area, perim)
Area: 15, Perimeter: 16
Variadic Functions
func sum(nums ...int) int {
total := 0
for _, n := range nums {
total += n
}
return total
}
fmt.Println(sum(1, 2, 3)) // 6
fmt.Println(sum(10, 20, 30, 40)) // 100
// Spread a slice with ...
numbers := []int{5, 10, 15}
fmt.Println(sum(numbers...)) // 30
Closures
Functions in Go can capture variables from their enclosing scope, creating closures. This is commonly used for counters, iterators, and middleware:
func makeCounter() func() int {
count := 0
return func() int {
count++
return count
}
}
counter := makeCounter()
fmt.Println(counter()) // 1
fmt.Println(counter()) // 2
fmt.Println(counter()) // 3
defer, panic, recover
defer schedules a function call to run when the enclosing function returns. It's used for cleanup — closing files, releasing locks, logging. Deferred calls execute in LIFO (last-in, first-out) order.
func readFile(path string) error {
f, err := os.Open(path)
if err != nil {
return err
}
defer f.Close() // Always runs, even if we return early with an error
// ... read file ...
return nil
}
panic and recover
panic causes a runtime crash (like throwing an unrecoverable exception). recover catches a panic in a deferred function. In practice, avoid panic — return errors instead. Use panic only for truly unrecoverable situations (corrupted state, missing critical config).
⚠️ Common Mistake: Defer in a loop
Deferred calls don't run until the function returns. In a loop processing 1000 files, defer f.Close() keeps all 1000 file handles open until the loop ends. Instead, wrap the body in a function or close explicitly.
Practice Exercises
Easy Hello World Variant
Modify the example to accept user input and print a personalized greeting.
Easy Code Reading
Read through the code examples above and predict the output before running them.
Medium Extend the Example
Take one code example and add error handling, input validation, or a new feature.