Video Coming Soon...

Created by Zed A. Shaw Updated 2025-10-07 14:32:54

19: The Type System: Closures

WARNING This exercise is in DRAFT status, so there may be errors. If you find any, please email me at help@learncodethehardway.com so I can fix them.

You're now going to learn about something that seems complicated, but isn't actually anything new. I'm warning you about this because everyone who tries to learn Closures seems to freak out and give it this complexity that isn't there. I'm going to walk you through the logic of how they work, and hopefully I can explain it simply so you understand it.

Step 1: A Function Has a Name

Do you remember when we made a function that adds two numbers like this:

func Add(a int, b int) int {
    result := a + b
    return result
}

In this code the function is named Add, and it has two variables named a and b. In this function I assign the result of a + b to the variable named result. I'm allowed to do this because a has a name, b has a name, and Go allows me to use anything with a name to create a new variable with a new name.

Given that Add is also a name then is there any reason I can't do this:

// all of these work
my_add := Add
another_add := Add
add_stuff := Add

If Go is a logical language then there shouldn't be any reaons why I couldn't do that. Add has a name, and just like a and b I can assign that to another variable.

YOU DO IT Stop right now and try this out to prove to yourself that you can actually assign a function to another name.

Step 2: Calling a Function by Any Name

When you call a function you add the () (parenthesis) to the end of the functions name, list any parameters, and Go runs it for you returning the result. If I do this:

ten := Add(5,5)

Then the variable ten will be the number 10.

If I am allowed to assign the function Add to a different variable, and I get no errors, then doesn't that mean I can also call that new varible the same way? This should work right?

my_add := Add
ten := my_add(5,5)

If Go is a logical language, and it allows me to assign a function to a new variable, then it'd have to let me call that new variable, otherwise the entire operation is pointless.

YOU DO IT Stop right now and call your newly named functions to prove to yourself that this does actually work.

Step 3: Functions Have a Type

If every variable in Go has a type--like a above is int--then it makes sense that a function would also have a type. What would that type be? For our Add function it's this:

var my_add func(int,int) int
my_add = Add

Let's break this down one thing at a time so you see how it's actually read:

  1. var -- Declare a variable.
  2. my_add -- Name of variable.
  3. func -- Start of the type func, but that's not enough. func types are more complex than just int.
  4. ( -- Same as in your function's definition, as in the same as when you defined the Add function.
  5. int -- You don't need the parameter name, just its type, and the first parameter is int.
  6. , -- Add has two parameters, so we need a , (comma).
  7. int -- int again since that's the type of the second parameter.
  8. ) -- Just like with the Add function, we end the parameter list with a ) (close parens).
  9. int -- The return value of Add is int, so we write the int here.

The parts of this line from 3-9 are what we call a "function signature." Just like with your handwritten signature it's a specific form that identifies this function as unique compared to other functions. In the case of my signature it's unique all the time.

Step 4: Function Signatures Can Have type

Remember when we created an Animal struct?

type Animal struct {
    Name string
    Age int
    // and so on
}

The type keyword creates a new type name for the struct here. If I can do that with a struct, then couldn't I also do that with the func (int,int) int above?

type MathFunc func (int, int) int

If that works, then that new type name can be used in a variable declaration just like with struct:

type MathFunc func (int, int) int

var my_add MathFunc
my_add = Add
result := my_add(5, 5)

YOU DO IT Stop now and create a variable like this, with a type and use it.

Step 5: Passing a Function to a Function

Great, we've established that you can do the following:

  1. You can assign a function to a new name just like anything else in Go.
  2. This new name can be called, just like the original function.
  3. This means that functions also have types like an int or struct.
  4. If a function has a type signature, then we can use the type keyword to name it for later (and to avoid figuring out complex function signatures).

This seems to indicate that anything I cand do with a variable I can do with a function, so what about passing a function to another function? Let's try it:

View Source file ex19a/main.go Only

package main

import (
    "fmt"
)

type MathFunc func (int, int) int

func Add(a int, b int) int {
    return a + b
}

func Sub(a int, b int) int {
    return a + b
}

func TenMath(math MathFunc) int {
    return math(10, 10)
}

func main() {
    using_add := TenMath(Add)
    fmt.Println("using_add is", using_add)

    using_sub := TenMath(Sub)
    fmt.Println("using_sub is", using_sub)
}

In this code I'm doing nothing we haven't covered, the only new part is:

func TenMath(math MathFunc) int {
    return math(10, 10)
}

func main() {
    using_add := TenMath(Add)
}

If everything is true so far, then the TenMath function should work because:

  1. Go lets us create a new type for a function's signature.
  2. Go lets us assign a function to a variable.
  3. We can declare that variable with the type.
  4. Functions take parameters that are variables.
  5. The parameters to a function have a type.
  6. That means I can give the parameters to a function using the type of MathFunction just like I would with a int.
  7. And that means, I can pass any function that has the same signature as MathFunction to another function as a parameter.

STOP At this point you should take a massive break and then study this code for as long as it takes to grasp this concept. It's incredibly simple and is a consequence of giving functions the same capabilities as any other variable. I recommend you spend time breaking, changing, and recreating this code until you get that you can pass functions to functions like this.

Step 6: Anonymous Functions

The next piece of this puzzle is anonymous functions, which is simply a function that doesn't have a name. These can be assigned directly to a variable or passed on the spot to a function. For example:

add := func (b int) int {
    return 10 + b
}

result := add(5) // should be 15

As you can see, you create an anonymous function the same way as other funtions, but you remove the name. If I wrote:

func AddTen(b int) int {
    // ...
}

Then to anonymize it I remove the AddTen. I then have to assign that to a variable or pass it as a value or it's not much use.

Step 7: Returning a Function from a Function

Are you seeing a trend with this lesson? Almost anything you can do with an int or struct you can do with a function. There are some restrictions, but for the most part, the functions in Go are what we call "first class functions." That means they aren't special fixed entities but as variable as anything else in the language.

For the next piece, remember our original Add() function:

func Add(a int, b int) int {
    result := a + b
    return result
}

This returned result to the caller right? We've established that you can do almost anything with functions that you can do with other variables, so why not returning one? More importantly, can we define a function inside another function? Let's try it:

View Source file ex19b/main.go Only

package main

import (
    "fmt"
)

type MathFunc func (int) int

func MakeAdder() MathFunc  {
    add := func (b int) int {
        return 10 + b
    }

    return add
}

func main() {
    add_ten := MakeAdder()

    ten_plus_ten := add_ten(10)
    fmt.Println("ten_plus_ten is", ten_plus_ten)

    ten_plus_5 := add_ten(5)
    fmt.Println("ten_plus_5 is", ten_plus_5)
}

This code is the culmination of what we've discovered so far about first class functions. The most important part is this:

func MakeAdder() MathFunc  {
    add := func (b int) int {
        return 10 + b
    }

    return add
}

Let's break this part down line-by-line:

Once we have this we can use this to create any number of functions, but this isn't too usefule. What is useful is Closures.

Step 8: And Therefore, a Closure Is...

The final piece of this puzzle is a question:

If you are allowed to create functions inside other functions (see add := ...), can that function use variables from outside it? How would returning such a function even work?

Let's make a small modification to the MakeAdder function so it takes a parameter to demonstrate this:

func MakeAdder(a int) MathFunc  {
    add := func (b int) int {
        return a + b
    }

    return add
}

When you make this change you have to change the main() code to call MakeAdder(10) instead.

The only change I made is MakeAdder(a int) is now a parameter that lets you configure the add function so it can add any number. Then I changed the return code to be return a + b instead of return 10 + b. That means the function MakeAdder returns is now using the variable a instead of 10.

But wait, won't that crash? When a function normally returns any of its parameters (in this case a and b) disappear. Trying to use them after would normally not be possible or cause a crash in most languages, but in Go and languages with "first class functions" this is possible because of closures.

A closure is:

  1. A function (child) defined inside another function (parent)...
  2. That references (uses) variables from the parent function...
  3. Which causes Go to "pack up" the variables being used and attach them to the returned function so the child function keeps working.

In this case, Go does this:

  1. Sees the parameters a and b to MakeAdder (parent).
  2. Sees the creation of add (child).
  3. Sees that add (child) is using the a variable from MakeAdder (parent).
  4. Attaches it to add so that it continues to exist after the return add at the end.
  5. Now any time you call ten_plus_ten you only pass 1 variable, but ten_plus_ten still has access to the original a from MakeAdder.

This is the only slightly hidden thing in all of this that's not something other variables get. Everything else so far has been a consequence of functions being treated with the same respect as variables (thus the name "first class", like on a plane). This feature is needed though because without it functions defined inside other functions wouldn't work right.

STOP! Once again, take time to understand this as it's subtle and simply done for you without any extra syntax. Try making your own MakeX function that makes these kinds of closures until you understand what's going on.

The Code

You're now ready to try another example of closures, and as usual I won't be breaking this down because there's nothing new in here.

View Source file ex19/main.go Only

package main

import (
    "fmt"
)

type CountFunc func() int

func MakeCounter(starting_at int) CountFunc {
    counter := func() int {
        starting_at++
        return starting_at
    }

    return counter
}

func main() {
    from_ten := MakeCounter(10)

    for i := 10; i < 20; i = from_ten() {
        fmt.Println(i)
    }
}

In this code I create a MakeCounter to demonstrate another aspect of closures. The variables that are attached to closures are persistent inside the closure, so you can to repeatedly generate a sequence.

The Practice

  1. Break It -- There's many ways to break this code, but try returning a function with one signature when the return type is different. You should also definitely cause Go to crash with a recursive function as described in the Tail Call Optimization section.
  2. Change It -- I think you should spend as much time as possible to change this code and experiment with closures until you get it. Try making new closures that do things like cycle through a slice of names or mutate a struct in some way.
  3. Recreate It -- I really feel like you will benefit greatly from recreating this code, but also from devising your own idea and making it from scratch.

Study Drills

  1. How insane can you go with this? Could you make a closure that makes closures that use other closures?
  2. How would you convert the fib() function to use a loop instead of the broken recursion?
Previous Lesson Next Lesson

Register for Learn Go the Hard Way

Register today for the course and get the all currently available videos and lessons, plus all future modules for no extra charge.