In Go, functions are values: you can pass a function to another function the same way you pass an int or a string, as long as the caller’s value matches the parameter’s function type (parameter types and result types in order). That is how you pass behavior instead of only data—validators, formatters, comparators, HTTP handlers, and retries all use the same idea. For declaring and calling ordinary functions first, see Functions in Go.
Tested on: recent Go on 64-bit Linux.
Quick answer: can functions be passed as parameters in Go?
Yes. Write the parameter as a func(...) type, then pass a named function, a variable that holds a function, or an anonymous function literal with the exact same signature.
package main
import "fmt"
func run(v int, op func(int) int) int {
return op(v)
}
func main() {
fmt.Println(run(3, func(n int) int { return n * 2 }))
}You should see 6.
Function parameter vs normal parameter
| Idea | Normal parameter | Function parameter |
|---|---|---|
| You pass | Data (int, string, structs) |
Behavior (code to run later) |
| Caller writes | process(42) |
process(42, func(n int) int { return n * 2 }) |
| callee receives | A value | A callable value matching func(...)... |
Function argument is the expression you pass at the call site; function parameter is the name and type in the callee’s signature. A function value is not a function call: pass f, not f(), unless you mean to execute f immediately and pass its return value.
Pass a named function as a parameter
Any package-level or local function is a value if its type matches. The compiler checks arity, parameter types, and result types—names of parameters in the function type do not have to match between the argument function and the parameter type.
package main
import (
"fmt"
"strings"
)
func firstLine(s string) string {
if i := strings.IndexByte(s, '\n'); i >= 0 {
return s[:i]
}
return s
}
func transform(s string, f func(string) string) string {
return f(s)
}
func main() {
raw := "hello\ngo"
fmt.Println(transform(raw, firstLine))
}You should see hello. Here firstLine is a named function value passed where func(string) string is expected.
Use a named function type
type Validator func(string) error declares a defined function type (sometimes called a named function type). It is not a struct: it names a func signature so you can reuse it in APIs without repeating long func(...) literals.
package main
import (
"errors"
"fmt"
"strings"
)
type Validator func(string) error
func chain(v string, validators ...Validator) error {
for _, fn := range validators {
if err := fn(v); err != nil {
return err
}
}
return nil
}
func main() {
notEmpty := func(s string) error {
if strings.TrimSpace(s) == "" {
return errors.New("empty")
}
return nil
}
fmt.Println(chain("ok", notEmpty))
fmt.Println(chain(" ", notEmpty))
}You should see <nil> then a non-nil error message.
Pass an anonymous function (callback)
Use a literal when the behavior is short and only needed at the call site. For syntax and immediate invocation, see anonymous functions in Go.
package main
import "fmt"
func withPrefix(prefix string, format func(string) string) {
fmt.Println(format(prefix))
}
func main() {
withPrefix("ID:", func(s string) string {
return s + "42"
})
}You should see ID:42. If the same logic is reused or grows large, promote it to a named function.
Callback functions in Go
A callback is a function you pass so the callee can run it later or after some step: after I/O, on validation failure, between retries, and so on.
| Pattern | Idea |
|---|---|
| Post-step | onSuccess() after work finishes |
| Guard | validate(x) before persisting |
| Retry | attempt() until a predicate passes |
| Transform | map/filter style helpers |
The standard library uses this heavily: for example sort.Slice takes a less func(i, j int) bool argument to compare elements without a new type every time.
package main
import (
"fmt"
"sort"
)
func main() {
names := []string{"bob", "Alice", "carol"}
sort.Slice(names, func(i, j int) bool {
return names[i] < names[j]
})
fmt.Println(names)
}You should see names sorted lexicographically (Alice first).
Real standard library shapes
| Package | Typical func parameter role |
|---|---|
sort |
less comparison, sort.Slice |
net/http |
HandlerFunc, middleware wrapping ResponseWriter and *Request |
filepath |
WalkDir walk function func(path string, d fs.DirEntry) error |
time |
AfterFunc runs a func() after a delay |
Small filepath.WalkDir example (creates a temp file under the default temp dir):
package main
import (
"fmt"
"io/fs"
"os"
"path/filepath"
)
func main() {
dir, err := os.MkdirTemp("", "walk-*")
if err != nil {
panic(err)
}
defer os.RemoveAll(dir)
_ = os.WriteFile(filepath.Join(dir, "a.txt"), []byte("x"), 0o644)
_ = filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
if !d.IsDir() {
fmt.Println("file:", d.Name())
}
return nil
})
}You should see a line such as file: a.txt.
Pass a method as a function parameter
Methods are functions with a receiver. You can pass them in two main ways:
Method value — receiver is fixed: t.Method has type func() if Method has no parameters beyond the receiver.
Method expression — pass the receiver later: T.Method has type func(T, ...) (receiver as first argument).
package main
import "fmt"
type Counter int
func (c Counter) Double() int { return int(c) * 2 }
func runTwice(f func() int) {
fmt.Println(f(), f())
}
func main() {
var c Counter = 3
runTwice(c.Double)
var me func(Counter) int = Counter.Double
fmt.Println(me(5))
}You should see doubled values from the method value, then 10 from the method expression call.
Function parameters that return errors
It is common to pass func(...) error so the caller supplies work that can fail while the outer function owns logging, order, retries, or wrapping (see the Validator chain above for func(string) error). The same idea appears as func() error when a step needs no inputs—migrations, cleanup hooks, or “try this once” probes.
package main
import (
"errors"
"fmt"
)
func runStep(name string, step func() error) error {
fmt.Println("Running:", name)
if err := step(); err != nil {
return fmt.Errorf("%s failed: %w", name, err)
}
fmt.Println("Completed:", name)
return nil
}
func main() {
err := runStep("connect database", func() error {
return errors.New("connection refused")
})
if err != nil {
fmt.Println(err)
}
}You should see lines starting with Running: and connect database failed: connection refused.
If the same func() error shape appears in several APIs, give it a defined type (like type Step func() error next to your Validator type)—same idea as the named function type section, not a struct.
Function parameter vs interface
Prefer a func parameter when… |
Prefer an interface when… |
|---|---|
| You need one or two operations | You need a small set of named methods |
| Callers only supply behavior | Callers supply a type with several methods |
| You want the lightest abstraction | You want mocking or multiple implementations |
See interfaces in Go for the wider pattern. A func is often the smallest step; an interface grows when behavior spans multiple calls.
Function parameters and generics
A function parameter swaps in behavior at runtime. Generics let one implementation work for many concrete types. Together, you keep one loop or pipeline and pass the type-specific rule as a func.
| Mechanism | Changes |
|---|---|
| Function parameter | What the code does (predicate, map, compare) |
| Type parameter | Which element type you are processing |
package main
import "fmt"
func filter[T any](items []T, keep func(T) bool) []T {
var out []T
for _, item := range items {
if keep(item) {
out = append(out, item)
}
}
return out
}
func main() {
nums := []int{1, 2, 3, 4, 5}
fmt.Println(filter(nums, func(n int) bool { return n%2 == 0 }))
names := []string{"go", "linux", "cloud"}
fmt.Println(filter(names, func(s string) bool { return len(s) > 2 }))
}You should see a slice of even numbers, then strings longer than two runes. The same filter body applies to both; only the keep function changes. Reach for generics when the algorithm is identical across types; add a function parameter for the one hook that varies. If the hook itself needs many methods, an interface may be clearer than a large func API.
Mistakes to avoid
Passing f() instead of f
f() runs immediately; you pass the return value, not the function, unless the parameter type is the result type.
Signature mismatch
Parameter and argument func types must match, including ... variadic forms and result lists.
Calling a nil function value
If a func field or variable is nil, invoking it panics. Guard with if f != nil when APIs allow omission.
Overloaded callbacks
If a callback grows many branches, replace it with named functions or an interface so readers can follow the flow.
Go function as parameter cheat sheet
| Situation | Pattern |
|---|---|
| One-off small behavior | Anonymous literal at call site |
| Reused behavior | Named func, pass by name |
| Repeated signature | type Op func(...)... |
| Sort / compare | sort.Slice, sort.SliceStable |
| HTTP | http.HandlerFunc, middleware func(http.Handler) http.Handler |
| Walk files | filepath.WalkDir callback |
| Delayed work | time.AfterFunc |
| Method with fixed receiver | Method value t.M |
| Receiver supplied later | Method expression T.M |
| Several methods | interface |
| Many types, same algorithm | Generics + func parameter |
Which pattern should you use?
| You need… | Use |
|---|---|
| “Run this after load” | func() callback parameter |
| “Is this string valid?” | func(string) error |
| “Compare two rows” | func(a, b T) bool |
| “Handle this route” | http.HandlerFunc or func(ResponseWriter, *Request) |
| “Several operations as a unit” | Interface |
Summary
Go passes functions as parameters by declaring a func(...)... parameter type and supplying a matching function value—named function, variable, or literal. That is different from passing normal data: you are handing the callee behavior to invoke. Named function types (type F func(...)) are not structs; they document repeated signatures. Methods become function values via method values and method expressions. Prefer a plain func when one operation suffices; prefer an interface when a group of methods defines the capability; combine with generics when types vary but the algorithm stays the same. The language spec calls out function types precisely.

