Gotchas and Common Mistakes with Closures in Go

defer and go statements take function calls as arguments

This is a pretty simple mistake, and it is one that the compiler will sometimes catch for you, but not always. When you use defer or go in golang you need to pass a function call as the argument, not just a function declaration.

If you are creating your closure inline it is hard to make this mistake because the compiler will catch it.

func main() {
  defer func() {
    fmt.Prinltn("teardown")
  }
}

This code will result in a compilation error expression in go/defer must be function call, so it is pretty hard to not catch, but let’s say you have a function that sets up your application server and returns a function that should be run right before your application exits in order to handle closing the database connection or tearing down anything else that needs torn down. That function might look something like this.

func setup() func() {
  fmt.Println("pretend to set things up")

  return func() {
    fmt.Println("pretend to tear things down")
  }
}

Now lets say we are using this in our main() function to setup our server, and we want to defer the returned function. This is where the mistake slips in, and you might need to examine the code closely to catch it.


func main() {
  defer setup()
}

func setup() func() {
  fmt.Println("pretend to set things up")

  return func() {
    fmt.Println("pretend to tear things down")
  }
}

What do you expect the output to be?

Go ahead and run the code. You can do so on the Go Playground right here. You will get the following output.

pretend to set things up

What went wrong? Why don’t we see the output pretend to tear things down?

It turns out both defer and go take a function call as an argument, not a function. This is important because it means that our code is actually defering the call to setup(), and it is never actually running the returned function.

Instead what we really want is the following.


package main

import "fmt"

func main() {
  defer setup()()
}

func setup() func() {
  fmt.Println("pretend to set things up")

  return func() {
    fmt.Println("pretend to tear things down")
  }
}

See the difference? It is really subtle - we need a second () after our call to setup() because we are telling our program to defer calling the function returned from setup().

A clearer way to show this might instead be:

f := setup()
defer f()

Variables declared in for loops are passed by reference

Note: This was address in Go 1.22, so newer versions of Go won’t have to worry about this issue, but if you are working on a project with Go 1.21 or a lower version you should be aware of this potential bug. You can see this in the Go playground by changing the Go version in the dropdown.

When you declare a new variable inside of a for loop, it is important to remember that the variables aren’t being redeclared with each iteration. Instead the variable is the same, but instead the value that is stored in the variable is being updated.

Let’s look at an example of how this can cause issues with your closures.


package main

import "fmt"

func main() {
  var functions []func()

  for i := 0; i < 10; i++ {
    functions = append(functions, func() {
      fmt.Println(i)
    })
  }

  for _, f := range functions {
    f()
  }
}

What do you expect this to output? Go ahead and run it on the Go Playground.

The output you will get is:

10
10
10
10
10
10
10
10
10
10

The issue we are experiencing here is that i is declared inside of a for loop, and it is being changed with each iteration of the for loop. When we finally call all of our functions in the functions slice they are all referencing the same i variable which was set to 10 in the last iteration of the for loop.

The same thing can happen if you use ranges. Here is a similar example, but it uses the keyword range.


package main

import "fmt"

func main() {
  ints := []int{1, 2, 3, 4}
  var functions []func()

  for _, val := range ints {
    functions = append(functions, func() {
      fmt.Println(val)
    })
  }

  for _, f := range functions {
    f()
  }
}

In this example our output will be:

4
4
4
4

This is caused by the same issue as before. Instead of using the value of val in each closure, we are referencing the variable which is being changed with each iteration of the loop.

So, how do we fix it? One way is to utilize the fact that function parameters in Go are passed by value. This means that if we called the function doStuff(i) inside of a for loop that it would pass the value of i into the function as a parameter at that specific time, and not a reference to the i variable.

Here is an example of this in action:


package main

import "fmt"

func main() {
  var functions []func()

  for i := 0; i < 10; i++ {
    functions = append(functions, build(i))
  }

  for _, f := range functions {
    f()
  }
}

func build(val int) func() {
  return func() {
    fmt.Println(val)
  }
}

What if we don’t want to create the build() function globally?

Unfortunately, this example required us to create the build() function globally. If we had to do this for every closure we wanted to create, our code might get pretty crowded quickly. Especially if we only wanted to create a really basic closure.

Luckily we can still solve this issue by using an anonymous function, but be careful with this approach as it can quickly become hard to read, understand, and maintain if it gets too big.

Here is the same example as before, but it uses an anonymous function to create the closure.


package main

import "fmt"

func main() {
  var functions []func()

  for i := 0; i < 10; i++ {
    functions = append(functions, func(val int) func() {
      return func() {
        fmt.Println(val)
      }
    }(i))
  }

  for _, f := range functions {
    f()
  }
}

Let’s take a moment to walk through what is happening here.

First we have declare a function inline that takes in an integer value and returns a function.

func(val int) func() {
  return func() {
    fmt.Println(val)
  }
}

This is just like any other anonymous function, except we don’t assign it to a variable. Instead we immediately call the function with i as the parameter being passed in. This is the (i) part right after the function declaration.

After the anonymous function is called it returns a func(), which is then appended to the functions slice with the line functions = append(functions, ...).

If that still seems confusing, lets look at another example using the same anonymous function, but this time we will assign it to a variable.


package main

import "fmt"

func main() {
  var functions []func()
  fn := func(val int) func() {
    return func() {
      fmt.Println(val)
    }
  }

  for i := 0; i < 10; i++ {
    functions = append(functions, fn(i))
  }

  for _, f := range functions {
    f()
  }
}

Notice how we aren’t adding fn to the functions slice, but we are passing in the return value of it which is a func().

It was also pointed out on Reddit that you can also solve this problem by creating a new variable and assigning it with the value of i. Below is an example of this approach.


package main

import "fmt"

func main() {
  var functions []func()

  for i := 0; i < 10; i++ {
    j := i
    functions = append(functions, func() {
      fmt.Println(j)
    })
  }

  for _, f := range functions {
    f()
  }
}

You could even use i as your new variable (as weird as that may seem at first), so your code could instead read i := i instead of j := i. This is called shadowing a variable, and can lead to some confusing bugs if abused.

That’s all folks

Hopefully after seeing these mistakes in action you will be able to catch them if they creep into your own code.

Unfortunately it was hard to demonstrate these examples without using code that is a little less beginner-friendly, so if you found anything hard to grasp don’t hesitate to email me - jon@calhoun.io - and I’ll be happy to try to clear things up!

Learn Web Development with Go!

Sign up for my mailing list and I'll send you a FREE sample from my course - Web Development with Go. The sample includes 19 screencasts and the first few chapters from the book.

You will also receive emails from me about Go coding techniques, upcoming courses (including FREE ones), and course discounts.

Avatar of Jon Calhoun
Written by
Jon Calhoun

Jon Calhoun is a full stack web developer who teaches about Go, web development, algorithms, and anything programming. If you haven't already, you should totally check out his Go courses.

Previously, Jon worked at several statups including co-founding EasyPost, a shipping API used by several fortune 500 companies. Prior to that Jon worked at Google, competed at world finals in programming competitions, and has been programming since he was a child.

More in this series

This post is part of the series, Closures in Go.

Spread the word

Did you find this page helpful? Let others know about it!

Sharing helps me continue to create both free and premium Go resources.

Want to discuss the article?

See something that is wrong, think this article could be improved, or just want to say thanks? I'd love to hear what you have to say!

You can reach me via email or via twitter.

Recent Articles All Articles Mini-Series Progress Updates Tags About Me Go Courses

©2024 Jonathan Calhoun. All rights reserved.