Chapter 41: Go Functions
Go: Functions.
Functions are the building blocks of every real Go program. Once you understand how to write, call, and organize functions in Go, you can start building actual useful tools, APIs, CLI apps, servers — everything.
Go functions are very clean, very explicit, very safe, and have some unique features that make them feel elegant compared to other languages.
Let’s go through everything like we’re pair-programming together — slowly, with many copy-paste examples, style notes, common patterns, gotchas, and real-world usage.
1. Basic Syntax – The Anatomy of a Go Function
|
0 1 2 3 4 5 6 7 8 9 |
func name(parameter1 type1, parameter2 type2) returnType { // body return someValue } |
Key parts:
- func keyword
- name starts with uppercase → exported (public, visible outside package)
- name starts with lowercase → unexported (private to package)
- parameters: name first, then type (opposite of C++/Java)
- return type(s) after parentheses
- multiple return values are very common (especially with errors)
- braces {} always required (even for one line)
2. Simple Examples – Start Here
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
package main import "fmt" // 1. No parameters, no return func sayHello() { fmt.Println("Namaste from Hyderabad! 🇮🇳") } // 2. Parameters, single return func add(a int, b int) int { return a + b } // 3. Multiple parameters of same type → short syntax func multiply(x, y int) int { return x * y } // 4. Multiple return values (idiomatic error handling) func divide(a, b float64) (float64, error) { if b == 0 { return 0, fmt.Errorf("division by zero") } return a / b, nil } func main() { sayHello() sum := add(17, 25) fmt.Println("17 + 25 =", sum) product := multiply(6, 7) fmt.Println("6 × 7 =", product) result, err := divide(100, 8) if err != nil { fmt.Println("Error:", err) } else { fmt.Printf("100 / 8 = %.2f\n", result) } // bad divide _, err = divide(100, 0) fmt.Println("Bad divide error:", err) } |
3. Named Return Values (Very Powerful & Idiomatic)
When you name the return values, they are automatically initialized to zero value and you can use naked return (return with no values).
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
func split(sum int) (x, y int) { x = sum * 4 / 9 y = sum - x return // naked return — returns named x and y } func main() { a, b := split(17) fmt.Println("Split 17 →", a, b) // 7 10 } |
Why named returns are loved:
- Very common in error handling + multiple returns
- Acts like “early named exit points”
- Makes signature self-documenting
4. Multiple Return Values – The Go Way
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
func getUserInfo(id int) (string, int, error) { if id <= 0 { return "", 0, fmt.Errorf("invalid id: %d", id) } // pretend database lookup name := "Webliance" age := 25 return name, age, nil } func main() { name, age, err := getUserInfo(1) if err != nil { fmt.Println("Error:", err) return } fmt.Printf("User: %s, Age: %d\n", name, age) // ignore name, only want age and error _, age2, err2 := getUserInfo(999) fmt.Println("Age2:", age2, "Error:", err2) } |
Blank identifier _ is used when you want to ignore a return value.
5. Variadic Functions (… – variable number of arguments)
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
func sum(numbers ...int) int { total := 0 for _, n := range numbers { total += n } return total } func main() { fmt.Println(sum(1, 2, 3)) // 6 fmt.Println(sum(10, 20)) // 30 fmt.Println(sum()) // 0 – empty slice // pass slice with ... vals := []int{1, 2, 3, 4} fmt.Println(sum(vals...)) // 10 } |
Only one variadic parameter and it must be last.
6. Functions as Values (First-class citizens)
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
func double(n int) int { return n * 2 } func triple(n int) int { return n * 3 } func apply(fn func(int) int, value int) int { return fn(value) } func main() { fmt.Println(apply(double, 7)) // 14 fmt.Println(apply(triple, 7)) // 21 } |
7. Anonymous Functions & Closures
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
func main() { // anonymous function greet := func(name string) { fmt.Println("Hello,", name) } greet("Webliance") // closure – captures outer variable counter := 0 increment := func() int { counter++ return counter } fmt.Println(increment()) // 1 fmt.Println(increment()) // 2 fmt.Println(increment()) // 3 } |
8. Best Practices & Common Patterns (2025–2026 style)
- Short functions – aim for < 30–40 lines
- Single responsibility – one function = one job
- Return early on errors
- Use named returns when there are 2+ return values
- Pass slices/maps by value (cheap – header copy)
- Use pointers only when you need to modify receiver or avoid copy cost
- Variadic + slice – most common variadic pattern
9. Quick Practice – Try Writing These
- Function that takes two numbers and returns sum, difference, product
- Function that returns (min, max) of a slice of ints
- Variadic function that concatenates strings with separator
- Closure that generates next Fibonacci number each call
Which function felt most natural to write?
Any part still confusing?
- Multiple returns vs single return + error struct?
- When to use named vs unnamed returns?
- Closures capturing variables – lifetime rules?
- Or ready for structs next?
Keep writing small functions — once you feel comfortable calling them, returning multiple values, and using closures, you’ll be able to build real programs very quickly.
You’re doing awesome — keep going! 💪🇮🇳🚀
