Golang Function as Parameter Explained with Examples

Pass functions as parameters in Go: func types, named and anonymous arguments, callbacks, defined function types, method values and expressions, errors, and when to prefer an interface or generics.

Published

Updated

Read time 8 min read

Reviewed byDeepak Prasad

Golang Function as Parameter Explained with Examples

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.

go
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 }))
}
Output

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.

go
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))
}
Output

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.

go
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))
}
Output

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.

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"
	})
}
Output

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.

go
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)
}
Output

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):

go
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
	})
}
Output

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).

go
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))
}
Output

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.

go
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)
	}
}
Output

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
go
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 }))
}
Output

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.


References

Tuan Nguyen

Data Scientist

Proficient in Golang, Python, Java, MongoDB, Selenium, Spring Boot, Kubernetes, Scrapy, API development, Docker, Data Scraping, PrimeFaces, Linux, Data Structures, and Data Mining. With expertise …