First-Class Functions
Functions as Valuesβ
In Go, functions are first-class citizens: they can be assigned to variables, passed as arguments, returned from other functions, and stored in data structures β exactly like integers or strings. This unlocks a wide range of patterns, from simple callbacks to full middleware pipelines, without requiring inheritance or interface boilerplate.
The type of a function is determined by its parameter types and return types. Two functions with the same parameter and return types share the same type, regardless of their names or bodies.
package main
import "fmt"
func double(n int) int { return n * 2 }
func square(n int) int { return n * n }
func negate(n int) int { return -n }
func main() {
// Assign a function to a variable
transform := double
fmt.Println(transform(5)) // 10
// Reassign to a different function of the same type
transform = square
fmt.Println(transform(5)) // 25
// Store functions in a slice
ops := []func(int) int{double, square, negate}
for _, op := range ops {
fmt.Printf("op(7) = %d\n", op(7))
}
// Store functions in a map
registry := map[string]func(int) int{
"double": double,
"square": square,
"negate": negate,
}
for name, fn := range registry {
fmt.Printf("%-8s 4 => %d\n", name, fn(4))
}
}
Output:
10
25
op(7) = 14
op(7) = 49
op(7) = -7
double 4 => 8
square 4 => 16
negate 4 => -4
Function Type Declarationsβ
Naming a function type makes code more readable and enables you to attach methods to it (which is exactly how http.HandlerFunc works in the standard library).
package main
import (
"fmt"
"strings"
)
// StringTransformer is a function type that transforms a string.
type StringTransformer func(string) string
// Pipeline applies a sequence of transformers left-to-right.
func pipeline(s string, transforms ...StringTransformer) string {
for _, t := range transforms {
s = t(s)
}
return s
}
// Concrete transformers matching StringTransformer.
func toUpper(s string) string { return strings.ToUpper(s) }
func trim(s string) string { return strings.TrimSpace(s) }
func exclaim(s string) string { return s + "!" }
func repeat(s string) string { return s + " " + s }
func main() {
result := pipeline(" hello world ", trim, toUpper, exclaim)
fmt.Println(result) // HELLO WORLD!
result2 := pipeline("go", toUpper, exclaim, repeat)
fmt.Println(result2) // GO! GO!
// Build a pipeline dynamically
var steps []StringTransformer
steps = append(steps, trim, toUpper)
if true { // condition could come from config
steps = append(steps, exclaim)
}
fmt.Println(pipeline(" dynamic pipeline ", steps...))
}
Output:
HELLO WORLD!
GO! GO!
DYNAMIC PIPELINE!
Passing Functions as Parameters (Higher-Order Functions)β
A function that accepts another function as a parameter is called a higher-order function. This is the backbone of functional patterns: map, filter, reduce, and the standard library's sort.Slice.
package main
import (
"fmt"
"sort"
)
// apply calls fn on every element of nums and returns a new slice.
func apply(nums []int, fn func(int) int) []int {
result := make([]int, len(nums))
for i, v := range nums {
result[i] = fn(v)
}
return result
}
// filter returns elements for which keep returns true.
func filter(nums []int, keep func(int) bool) []int {
var result []int
for _, v := range nums {
if keep(v) {
result = append(result, v)
}
}
return result
}
// reduce folds nums into a single value using fn, starting from initial.
func reduce(nums []int, initial int, fn func(acc, val int) int) int {
acc := initial
for _, v := range nums {
acc = fn(acc, v)
}
return acc
}
func main() {
nums := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
doubled := apply(nums, func(n int) int { return n * 2 })
fmt.Println("doubled:", doubled)
evens := filter(nums, func(n int) bool { return n%2 == 0 })
fmt.Println("evens:", evens)
sum := reduce(nums, 0, func(acc, v int) int { return acc + v })
fmt.Println("sum:", sum)
product := reduce([]int{1, 2, 3, 4, 5}, 1, func(acc, v int) int { return acc * v })
fmt.Println("product:", product)
// sort.Slice uses a higher-order function for the comparison
type Person struct {
Name string
Age int
}
people := []Person{
{"Alice", 32},
{"Bob", 25},
{"Carol", 28},
{"Dave", 25},
}
// Sort by age, then by name for equal ages
sort.Slice(people, func(i, j int) bool {
if people[i].Age != people[j].Age {
return people[i].Age < people[j].Age
}
return people[i].Name < people[j].Name
})
fmt.Println("\nSorted people:")
for _, p := range people {
fmt.Printf(" %-8s %d\n", p.Name, p.Age)
}
}
Output:
doubled: [2 4 6 8 10 12 14 16 18 20]
evens: [2 4 6 8 10]
sum: 55
product: 120
Sorted people:
Bob 25
Dave 25
Carol 28
Alice 32
Functions Returning Functions (Function Factories)β
Functions that return other functions are called function factories or generators. They produce specialised behaviour on demand without global state or configuration structs.
package main
import (
"fmt"
"strings"
)
// multiplier returns a function that multiplies by factor.
func multiplier(factor int) func(int) int {
return func(n int) int {
return n * factor
}
}
// adder returns a function that adds delta to its argument.
func adder(delta int) func(int) int {
return func(n int) int { return n + delta }
}
// prefixer returns a StringTransformer that prepends prefix.
func prefixer(prefix string) func(string) string {
return func(s string) string { return prefix + s }
}
// between returns a predicate that tests lo <= n <= hi.
func between(lo, hi int) func(int) bool {
return func(n int) bool { return n >= lo && n <= hi }
}
func main() {
triple := multiplier(3)
times10 := multiplier(10)
fmt.Println(triple(7)) // 21
fmt.Println(times10(7)) // 70
add5 := adder(5)
sub3 := adder(-3)
fmt.Println(add5(10)) // 15
fmt.Println(sub3(10)) // 7
errPrefix := prefixer("[ERROR] ")
infoPrefix := prefixer("[INFO] ")
fmt.Println(errPrefix("disk full"))
fmt.Println(infoPrefix("server started"))
inRange := between(18, 65)
ages := []int{10, 18, 30, 65, 66, 100}
for _, age := range ages {
fmt.Printf("age %-3d in range: %v\n", age, inRange(age))
}
// Compose two functions
compose := func(f, g func(int) int) func(int) int {
return func(n int) int { return f(g(n)) }
}
tripleAndAdd5 := compose(add5, triple)
fmt.Println("\ntripleAndAdd5(4):", tripleAndAdd5(4)) // (4*3)+5 = 17
_ = strings.ToUpper // keep strings import used
}
Output:
21
70
15
7
[ERROR] disk full
[INFO] server started
age 10 in range: false
age 18 in range: true
age 30 in range: true
age 65 in range: true
age 66 in range: false
age 100 in range: false
tripleAndAdd5(4): 17
Anonymous Functions and Immediately Invoked Expressionsβ
Anonymous functions (function literals) have no name. They can be assigned to a variable, passed directly as an argument, or invoked immediately after declaration.
package main
import "fmt"
func main() {
// Assigned to a variable
square := func(n int) int { return n * n }
fmt.Println(square(9)) // 81
// Passed directly as an argument
nums := []int{5, 2, 8, 1, 9, 3}
applyAll := func(data []int, fn func(int)) {
for _, v := range data {
fn(v)
}
}
applyAll(nums, func(n int) {
fmt.Printf("%d squared = %d\n", n, n*n)
})
// Immediately Invoked Function Expression (IIFE)
result := func(a, b int) int {
return a*a + b*b
}(3, 4)
fmt.Println("3Β²+4Β² =", result) // 25
// IIFE for complex initialisation
config := func() map[string]string {
m := make(map[string]string)
m["env"] = "production"
m["host"] = "api.example.com"
m["port"] = "443"
return m
}()
fmt.Println("config:", config)
}
Output:
81
5 squared = 25
2 squared = 4
8 squared = 64
1 squared = 1
9 squared = 81
3 squared = 9
3Β²+4Β² = 25
config: map[env:production host:api.example.com port:443]
IIFEs are useful for initialising package-level variables that require more than a simple expression, and for scoping temporary variables within a block.
Practical Example: sort.Slice and Custom Sortingβ
sort.Slice is the canonical Go example of passing a function as a parameter. It requires no interface implementation β just a closure that knows how to compare two elements.
package main
import (
"fmt"
"sort"
"strings"
)
type Product struct {
Name string
Category string
Price float64
Stock int
}
func main() {
products := []Product{
{"Widget", "hardware", 9.99, 150},
{"Gadget", "electronics", 49.99, 30},
{"Doohickey", "hardware", 19.99, 75},
{"Thingamajig", "electronics", 99.99, 10},
{"Whatchamacallit", "misc", 4.99, 200},
{"Gizmo", "electronics", 49.99, 50},
}
// Sort by price ascending
sort.Slice(products, func(i, j int) bool {
return products[i].Price < products[j].Price
})
fmt.Println("By price (asc):")
for _, p := range products {
fmt.Printf(" %-20s $%.2f\n", p.Name, p.Price)
}
// Sort by category, then name within category
sort.Slice(products, func(i, j int) bool {
ci, cj := products[i].Category, products[j].Category
if ci != cj {
return ci < cj
}
return products[i].Name < products[j].Name
})
fmt.Println("\nBy category + name:")
for _, p := range products {
fmt.Printf(" %-12s %-20s $%.2f\n", p.Category, p.Name, p.Price)
}
// sort.SliceStable preserves original order for equal elements
sort.SliceStable(products, func(i, j int) bool {
return strings.ToLower(products[i].Name) < strings.ToLower(products[j].Name)
})
fmt.Println("\nAlphabetical (stable):")
for _, p := range products {
fmt.Printf(" %s\n", p.Name)
}
}
Output:
By price (asc):
Whatchamacallit $4.99
Widget $9.99
Doohickey $19.99
Gadget $49.99
Gizmo $49.99
Thingamajig $99.99
By category + name:
electronics Gadget $49.99
electronics Gizmo $49.99
electronics Thingamajig $99.99
hardware Doohickey $19.99
hardware Widget $9.99
misc Whatchamacallit $4.99
Alphabetical (stable):
Doohickey
Gadget
Gizmo
Thingamajig
Whatchamacallit
Widget
Practical Example: Middleware Patternβ
HTTP middleware in Go is built entirely on first-class functions. Each middleware wraps a handler, adding behaviour before and/or after the inner handler runs.
package main
import (
"fmt"
"log"
"net/http"
"time"
)
// Middleware is a function that wraps an http.Handler.
type Middleware func(http.Handler) http.Handler
// Chain applies middlewares left-to-right, so the first middleware
// in the list is the outermost (executes first on request).
func chain(h http.Handler, middlewares ...Middleware) http.Handler {
// Apply in reverse so execution order matches declaration order
for i := len(middlewares) - 1; i >= 0; i-- {
h = middlewares[i](h)
}
return h
}
// logging logs the method, path, and duration of each request.
func logging(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
next.ServeHTTP(w, r)
log.Printf("%s %s %v", r.Method, r.URL.Path, time.Since(start))
})
}
// recovery catches panics and returns a 500 response.
func recovery(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
defer func() {
if err := recover(); err != nil {
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
log.Printf("panic: %v", err)
}
}()
next.ServeHTTP(w, r)
})
}
// corsHeaders adds permissive CORS headers for demonstration.
func corsHeaders(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE")
next.ServeHTTP(w, r)
})
}
func helloHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "Hello, middleware world!")
}
func main() {
hello := http.HandlerFunc(helloHandler)
// Apply middleware chain: logging β recovery β corsHeaders β hello
wrapped := chain(hello, logging, recovery, corsHeaders)
mux := http.NewServeMux()
mux.Handle("/hello", wrapped)
fmt.Println("Middleware chain assembled:")
fmt.Println(" logging -> recovery -> corsHeaders -> helloHandler")
fmt.Println("Server would listen on :8080 (not started in this demo)")
_ = mux
}
Output:
Middleware chain assembled:
logging -> recovery -> corsHeaders -> helloHandler
Server would listen on :8080 (not started in this demo)
Practical Example: Event Handler Registrationβ
Function values make it straightforward to build an event system without interfaces or reflection.
package main
import "fmt"
// EventType names the kinds of events the bus handles.
type EventType string
const (
EventUserCreated EventType = "user.created"
EventUserDeleted EventType = "user.deleted"
EventOrderPlaced EventType = "order.placed"
)
// Event carries the event type and arbitrary payload.
type Event struct {
Type EventType
Payload any
}
// HandlerFunc is the signature all event handlers must satisfy.
type HandlerFunc func(Event)
// EventBus dispatches events to registered handlers.
type EventBus struct {
handlers map[EventType][]HandlerFunc
}
func NewEventBus() *EventBus {
return &EventBus{handlers: make(map[EventType][]HandlerFunc)}
}
// Subscribe registers fn to be called whenever events of typ are published.
func (b *EventBus) Subscribe(typ EventType, fn HandlerFunc) {
b.handlers[typ] = append(b.handlers[typ], fn)
}
// Publish calls all handlers registered for event.Type.
func (b *EventBus) Publish(event Event) {
for _, fn := range b.handlers[event.Type] {
fn(event)
}
}
func main() {
bus := NewEventBus()
// Register handlers using function literals
bus.Subscribe(EventUserCreated, func(e Event) {
fmt.Printf("[AUDIT] User created: %v\n", e.Payload)
})
bus.Subscribe(EventUserCreated, func(e Event) {
fmt.Printf("[EMAIL] Welcome email queued for: %v\n", e.Payload)
})
bus.Subscribe(EventUserDeleted, func(e Event) {
fmt.Printf("[AUDIT] User deleted: %v\n", e.Payload)
})
bus.Subscribe(EventOrderPlaced, func(e Event) {
fmt.Printf("[ORDERS] Processing order: %v\n", e.Payload)
})
// Publish events
bus.Publish(Event{EventUserCreated, "alice@example.com"})
bus.Publish(Event{EventOrderPlaced, map[string]any{"id": "ORD-001", "total": 49.99}})
bus.Publish(Event{EventUserDeleted, "bob@example.com"})
}
Output:
[AUDIT] User created: alice@example.com
[EMAIL] Welcome email queued for: alice@example.com
[ORDERS] Processing order: map[id:ORD-001 total:49.99]
[AUDIT] User deleted: bob@example.com
Expert Tipsβ
http.HandlerFunc is the canonical named function type. Study how the standard library defines it: type HandlerFunc func(ResponseWriter, *Request) with a ServeHTTP method. This pattern β name a function type and attach methods to it β turns plain functions into interface implementations without any extra wrapper struct.
Prefer function types over single-method interfaces for callbacks. If an interface has exactly one method, consider accepting a function value instead. It is simpler to call and easier to test with anonymous functions.
Function values are comparable to nil, not to each other. You can check if fn == nil to guard against unset callbacks, but you cannot compare two non-nil function values for equality β the compiler will reject it.
Anonymous functions capture their enclosing scope. Any variable referenced inside an anonymous function is captured by reference. Changes to that variable after the function is defined are visible inside the function when it executes. This leads to the classic loop closure trap covered in the Closures chapter.
Use sort.Slice for one-off sorts; implement sort.Interface for reusable types. When sorting the same type in multiple places with the same ordering, embedding the comparison logic in a named type that satisfies sort.Interface is cleaner and more testable than scattering closures throughout your code.