Question
In Java, I can write something like this to pass behavior into another method:
someMethod(new Runnable() {
public void run() {
// run this sometime later
}
});
This allows the method to store or execute that code later, although using an anonymous inner class can be awkward.
Does Go provide a way to pass a function or callback as a parameter so that it can be executed later?
Short Answer
By the end of this page, you will understand that functions are first-class values in Go, which means you can store them in variables, pass them into other functions, and call them later. You will also see how Go callbacks compare to Java's Runnable style, how to declare function parameters, and how closures make callback-style code practical and concise.
Concept
In Go, functions can be passed as parameters just like numbers, strings, or structs. This works because functions are first-class values.
That means you can:
- assign a function to a variable
- pass a function into another function
- return a function from a function
- call a function later
This is Go's simple and direct way to support callbacks.
In Java, older callback patterns often use interfaces such as Runnable, especially before lambdas became common. In Go, you usually do not need a special interface just to pass executable behavior. You can pass the function itself.
For example, if a function expects another function as a parameter, you can write its parameter type like this:
func doLater(task func()) {
task()
}
Here, task is a parameter whose type is func(), meaning:
func= function type()= it takes no arguments- no return type = it returns nothing
This matters in real programming because many common tasks use callbacks or delayed behavior, including:
- event handling
- sorting with custom logic
- processing collections
- retry logic
- goroutines and concurrency helpers
- middleware and hooks
Go keeps this pattern lightweight. Instead of wrapping behavior in a class, you often just pass a function literal directly.
Mental Model
Think of a function in Go like a recipe card.
- A normal value like
5is a finished ingredient. - A function value is a set of instructions someone can use later.
When you pass a function as a parameter, you are handing another part of the program a recipe card and saying:
"Use these instructions when you're ready."
If the function also remembers values from where it was created, that is called a closure. It is like a recipe card with handwritten notes already attached.
Syntax and Examples
In Go, you declare a function parameter by writing its function type.
Basic syntax
func execute(task func()) {
task()
}
This means execute accepts a function that:
- takes no parameters
- returns no value
Passing a named function
package main
import "fmt"
func sayHello() {
fmt.Println("Hello")
}
func execute(task func()) {
task()
}
func main() {
execute(sayHello)
}
What happens?
sayHellois a normal function.executeaccepts a function parameter.execute(sayHello)passes the function itself, not the result.- Inside
execute,task()runs it.
Passing an anonymous function
package main
import "fmt"
func execute(task func()) {
task()
}
func main() {
execute(func() {
fmt.Println("Run this later")
})
}
This is the Go equivalent of passing behavior inline.
Function with parameters and return values
package main
import "fmt"
func apply(a int, b int, operation func(int, int) int) int {
return operation(a, b)
}
func main() {
result := apply(3, 4, func(x int, y int) int {
return x + y
})
fmt.Println(result)
}
Here the callback type is func(int, int) int, meaning:
- takes two
intvalues - returns one
int
Using a closure
package main
import "fmt"
func execute(task func()) {
task()
}
func main() {
name := "Go"
execute(func() {
fmt.Println("Hello,", name)
})
}
The anonymous function uses name from the surrounding scope. That remembered access is what makes closures so useful.
Step by Step Execution
Consider this example:
package main
import "fmt"
func runTask(task func()) {
fmt.Println("Before task")
task()
fmt.Println("After task")
}
func main() {
message := "Callback executed"
runTask(func() {
fmt.Println(message)
})
}
Step-by-step
-
The program starts in
main(). -
messageis created with the value"Callback executed". -
runTask(...)is called. -
The argument passed to
runTaskis an anonymous function:func() { fmt.Println(message) } -
Inside
runTask, the line below runs first:fmt.Println("Before task")Output so far:
Before task -
Then
task()is called. This executes the anonymous function passed frommain. -
That anonymous function prints
message, which it can access from the outer scope.Output now:
Before task Callback executed -
Control returns to
runTask. -
The final print statement runs:
fmt.Println("After task") -
Final output:
Before task
Callback executed
After task
This example shows the key idea: a function can be passed into another function and invoked later inside it.
Real World Use Cases
Passing functions as parameters is common in Go.
1. Custom sorting or processing
You may want to define custom logic for comparing or transforming values.
numbers := []int{5, 2, 9}
A helper function could accept a callback to decide how values are handled.
2. Retry logic
A reusable retry function can accept the operation to attempt.
func retry(task func() error) error {
return task()
}
This lets you reuse the retry mechanism for API calls, file reads, or database operations.
3. HTTP middleware and handlers
Web applications often pass functions around to wrap behavior such as:
- authentication
- logging
- rate limiting
- error recovery
4. Deferred cleanup helpers
You might pass cleanup actions into utility functions.
5. Asynchronous or concurrent work
Functions are often passed into goroutines:
go func() {
fmt.Println("Running concurrently")
}()
Even though this is not a parameter example by itself, it uses the same idea that functions are values.
6. Collection-style utilities
While Go does not have built-in map and filter for slices, developers often write helper functions that take callbacks for transforming or checking items.
Real Codebase Usage
In real Go codebases, function parameters are often used in practical, maintainable patterns.
Guarded execution
A helper may validate inputs before running a callback.
func withValidUser(name string, action func()) {
if name == "" {
return
}
action()
}
This uses an early return to avoid unnecessary work.
Error-producing callbacks
A very common pattern is passing functions that return error.
func run(task func() error) error {
return task()
}
This fits naturally with Go's error handling style.
Dependency injection for testability
Instead of hardcoding behavior, a function can receive another function that performs work.
func fetchData(load func() string) string {
return load()
}
In tests, developers can pass a fake function.
Wrapping behavior
A function can decorate another function.
func logWrapper(task func()) func() {
return func() {
fmt.Println("Starting")
task()
fmt.Println("Finished")
}
}
This is a common pattern in middleware, logging, and instrumentation.
Closures for configuration
Functions often capture configuration values.
func makeGreeter(prefix string) func(string) {
return func(name string) {
fmt.Println(prefix, name)
}
}
This is useful when you want reusable behavior with some values already pre-filled.
Common Mistakes
1. Calling the function instead of passing it
Broken code:
execute(sayHello())
Why it is wrong:
sayHello()calls the function immediately- its result is passed instead of the function itself
- if
sayHelloreturns nothing, the code will not compile
Correct code:
execute(sayHello)
2. Using the wrong function signature
Broken code:
func execute(task func()) {}
func greet(name string) {}
execute(greet)
Why it is wrong:
executeexpectsfunc()greetisfunc(string)- the signatures must match exactly
3. Forgetting that closures capture outer variables
Example:
func main() {
name := "Go"
f := func() {
fmt.Println(name)
}
name = "Golang"
f()
}
Output:
Golang
Why this surprises beginners:
- the closure refers to the variable, not a frozen copy of the original value
4. Making callback code harder than necessary
Beginners coming from Java or C# may try to recreate interface-heavy callback structures. In Go, a plain function parameter is often simpler.
Too heavy for many simple cases:
type Runner interface {
Run()
}
Often simpler:
func execute(task func()) {
task()
}
5. Ignoring nil function values
Function variables can be nil.
Broken code:
var task func()
task()
This will panic at runtime.
Safer version:
if task != nil {
task()
}
Comparisons
Go function callbacks vs Java Runnable
| Idea | Go | Java classic style |
|---|---|---|
| Pass behavior | Pass a function directly | Often pass an object implementing an interface |
| Syntax | Usually short | Often more verbose |
| Inline logic | Anonymous function | Anonymous inner class or lambda |
| State capture | Closures | Anonymous class or lambda capture |
Named function vs anonymous function
| Option | When to use | Example |
|---|---|---|
| Named function | Reusable logic | execute(sayHello) |
| Anonymous function | One-off callback | execute(func() { fmt.Println("hi") }) |
Function parameter vs interface
| Approach | Best for | Notes |
|---|---|---|
| Function parameter | One specific action | Simple and direct |
| Interface | Multiple related behaviors | Better when several methods are needed |
Closure vs regular function
| Type | Can access outer variables? | Common use |
|---|---|---|
| Regular function | No | General reusable behavior |
| Closure | Yes | Customized callbacks and wrappers |
Cheat Sheet
Core syntax
func use(task func()) {
task()
}
Function type examples
func() // no params, no return
func(int) // one int param
func(string) error // one string param, returns error
func(int, int) int // two ints in, one int out
Pass a named function
func hello() {
fmt.Println("hello")
}
use(hello)
Pass an anonymous function
use(func() {
fmt.Println("hello")
})
Return a function
func makePrinter(msg string) func() {
return func() {
fmt.Println(msg)
}
}
Important rules
- Functions are values in Go.
- Function signatures must match exactly.
- Use
nameto pass a function. - Use
name()to call a function. - Anonymous functions can form closures.
- A function variable can be
nil.
Common edge cases
var f func()
if f != nil {
f()
}
func apply(op func(int, int) int) int {
return op(1, 2)
}
FAQ
Can you pass a function as an argument in Go?
Yes. Functions are first-class values in Go, so they can be passed to other functions as parameters.
How do callbacks work in Go?
A callback is usually just a function passed into another function, which then calls it at the appropriate time.
Does Go need interfaces like Java Runnable for simple callbacks?
No. If you only need one action, passing a function directly is usually simpler than defining an interface.
Can anonymous functions be used in Go?
Yes. Go supports anonymous functions, and they are commonly used for inline callbacks.
Can a Go function remember values from its surrounding scope?
Yes. This is called a closure. The function can access variables from the place where it was created.
What happens if I call a nil function in Go?
Your program will panic at runtime. Check whether the function is nil before calling it if that is possible in your code.
Can a function parameter return a value in Go?
Yes. For example, you can accept a parameter of type func(int, int) int and use its returned result.
Is passing functions common in real Go programs?
Yes. It is widely used in middleware, retries, helper utilities, testing, and custom processing logic.
Mini Project
Description
Build a small task runner that accepts functions as parameters and executes them in order. This demonstrates how Go uses function values for callbacks without needing interface-heavy patterns.
Goal
Create a program that stores several tasks as functions and runs them one by one.
Requirements
- Define a function that accepts a callback of type
func(). - Create at least one named function task.
- Pass at least one anonymous function as a task.
- Print messages before and after each task runs.
- Run multiple tasks from
main().
Keep learning
Related questions
Are Go Maps Passed by Value or Reference?
Learn Go map parameter and return semantics: maps are passed by value, while copies share underlying data. Know when map pointers are useful.
Authentication for Go REST APIs: Sessions, Tokens, and Middleware
Learn practical authentication patterns for Go REST APIs, including password hashing, server sessions, bearer tokens, middleware, and security rules.
Automatic Build Versioning in Go: Embed Incrementing Build Numbers
Learn how to add automatic build versioning in Go using linker flags, build metadata, CI counters, and Git-based version values.