Functional Programming in Go: From A to Z
Go (Golang) is a language often associated with imperative and concurrent programming. However, its flexible type system and first-class functions allow you to successfully apply a functional style. In this article, we will explore how to use functional programming (FP) in Go: from basic concepts to practical examples. You will learn how to write cleaner, more predictable, and testable code.
What is Functional Programming in the Context of Go?
Functional programming is a paradigm where computations are built on the application of functions, avoiding state changes and side effects. In Go, this is expressed through:
- Pure functions — the result depends only on arguments, no mutation of external data.
- Higher-order functions — functions that take other functions as arguments or return them.
- Closures — anonymous functions that "remember" variables from the outer scope.
- Data immutability — preferring copying over modification.
Although Go is not a purely functional language (it has no monads or algebraic data types), it provides enough tools to effectively use FP approaches.
Anonymous Functions and Closures
In Go, functions are first-class objects. They can be assigned to variables, passed to other functions, and returned. Anonymous functions (lambdas) are the foundation of the functional style.
Example of an Anonymous Function
package main
import "fmt"
func main() { // Anonymous function assigned to a variable multiply := func(x, y int) int { return x * y }
result := multiply(5, 3) fmt.Println(result) // 15}Closures
A closure is an anonymous function that "captures" variables from the outer scope. This is a powerful tool for creating generators and functions with state.
package main
import "fmt"
// Sequence generatorfunc sequence() func() int { i := 0 return func() int { i++ return i }}
func main() { next := sequence() fmt.Println(next()) // 1 fmt.Println(next()) // 2 fmt.Println(next()) // 3
// Each call to sequence() creates a new closure next2 := sequence() fmt.Println(next2()) // 1}Closures are ideal for encapsulating state without using global variables — this is the foundation of the functional approach in Go.
Pure Functions and Immutability
A pure function has no side effects and does not modify passed data. In Go, this requires a conscious approach, as slices and maps are passed by reference.
Example of a Pure Function (Without Mutation)
package main
import "fmt"
// Pure function: does not modify the original slicefunc doubleValues(input []int) []int { result := make([]int, len(input)) for i, v := range input { result[i] = v * 2 } return result}
func main() { original := []int{1, 2, 3, 4} doubled := doubleValues(original)
fmt.Println("Original:", original) // [1 2 3 4] fmt.Println("Doubled:", doubled) // [2 4 6 8]}Notice: we create a new slice instead of modifying the original. This ensures immutability — a key principle of FP.
Higher-Order Functions: map, filter, reduce
Although Go does not have built-in map/filter/reduce methods for slices, they are easy to implement yourself.
package main
import "fmt"
// Map — applies a function to each elementfunc Map[T any](items []T, fn func(T) T) []T { result := make([]T, len(items)) for