Question

Passing Functions as Parameters in Go

gofunction

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 5 is 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.

Take Quiz

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?

  • sayHello is a normal function.
  • execute accepts 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 int values
  • 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

  1. The program starts in main().

  2. message is created with the value "Callback executed".

  3. runTask(...) is called.

  4. The argument passed to runTask is an anonymous function:

    func() {
        fmt.Println(message)
    }
    
  5. Inside runTask, the line below runs first:

    fmt.Println("Before task")
    

    Output so far:

    Before task
    
  6. Then task() is called. This executes the anonymous function passed from main.

  7. That anonymous function prints message, which it can access from the outer scope.

    Output now:

    Before task
    Callback executed
    
  8. Control returns to runTask.

  9. The final print statement runs:

    fmt.Println("After task")
    
  10. 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.

Take Quiz

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 sayHello returns 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:

  • execute expects func()
  • greet is func(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

IdeaGoJava classic style
Pass behaviorPass a function directlyOften pass an object implementing an interface
SyntaxUsually shortOften more verbose
Inline logicAnonymous functionAnonymous inner class or lambda
State captureClosuresAnonymous class or lambda capture

Named function vs anonymous function

OptionWhen to useExample
Named functionReusable logicexecute(sayHello)
Anonymous functionOne-off callbackexecute(func() { fmt.Println("hi") })

Function parameter vs interface

ApproachBest forNotes
Function parameterOne specific actionSimple and direct
InterfaceMultiple related behaviorsBetter when several methods are needed

Closure vs regular function

TypeCan access outer variables?Common use
Regular functionNoGeneral reusable behavior
ClosureYesCustomized callbacks and wrappers
Take Quiz

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 name to 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.

Take Quiz

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().
Take Quiz

Keep learning

Related questions

View more