Stage 1 · Code
Language Depth II
Closures and Anonymous Functions
Learn how functions can be created on the spot, remember values from the place they were created, and sometimes surprise you when a loop variable is reused.
Functions You Create on the Spot
Sometimes you do not need to invent a whole new named function and hang it on the wall forever. Sometimes you just need a tiny piece of behavior right here, right now, next to the code that uses it. An anonymous function is exactly that: a function literal with no name of its own.
Think of it like writing a short sticky note instead of printing a permanent sign. The note still contains real instructions. It just exists for one specific moment or one specific place in the program.
func(name string) string { ... }is a function value, just like42is an int value and"go"is a string value.- You can store that function in a variable, pass it into another function, return it from a function, or call it immediately.
- Anonymous does not mean weak or temporary in capability. It only means 'this function literal does not have its own top-level name.'
What a Closure Remembers
A closure is what you get when a function uses variables from the surrounding scope and keeps access to them after that outer code has moved on. The function 'closes over' those variables. In plain English: it carries a little backpack of outside state with it.
The key detail is that a closure captures the variable, not a frozen screenshot of its text. If the captured variable changes, later calls see the new value of that same variable.
Do not picture a closure as copying text out of the outer function. Picture it as holding onto specific variables from that outer scope. If those variables change, the closure observes the updated values on later calls.
Why Closures Are Useful
Closures are useful whenever behavior and state belong together. Instead of creating a tiny struct plus a method for every small job, you can sometimes return a function that quietly carries the state it needs.
| Pattern | Captured value | Why the closure helps |
|---|---|---|
| Counter generator | current | Each returned function gets its own private running total |
| Deferred cleanup callback | resource | You can describe later cleanup right where the resource is created |
| Memoization helper | cache | Repeated calls can reuse earlier work without a global variable |
The Loop Variable Capture Gotcha
This is the closure bug that has confused generations of Go learners. The short version is: if several closures all capture the same loop variable, they may all observe that one shared variable after the loop has already moved on.
To make that feel concrete, imagine one whiteboard marker at the front of the room. Each loop iteration does not always receive a brand-new marker. In older Go semantics, the loop often reused the same marker, just erasing and rewriting the value on each trip. Every closure pointed at that one marker, so later they all read whatever was written there last.
Go 1.22 changed this common case. When the loop variables are declared by the loop itself, each iteration now gets fresh variables. That means the same example produces api, worker, and cron on modern Go.
There is one more subtlety worth knowing: even on modern Go, you can still recreate the old bug if the variable lives outside the loop and the loop keeps assigning into that same outer variable. New Go versions fixed the common loop-declared case, not every possible shared-variable pattern in the universe.
Real teams maintain old services, libraries, and tutorials written before Go 1.22. Even if your current compiler handles range loops more safely, you still need to be able to read older code, spot the capture pattern, and know why the explicit shadowing fix works.
Quiz
What makes a function a closure?
Practice
Write a function `BuildLabeler(prefix string) func(string) string` that returns a closure. Each time the returned function is called with a name, it should produce `<prefix>: <name>`. For example, if `prefix` is `task`, then calling the returned function with `backup` should return `task: backup`.
Summary
- Anonymous functions let you define behavior right where you need it.
- A closure captures variables from an enclosing scope and keeps using that state later.
- Closures are handy for stateful helpers such as counters, deferred callbacks, and memoized functions.
- The classic loop-capture bug happened because multiple closures shared one reused loop variable.
- Go 1.22 fixed the common loop-declared case, but you should still understand the old behavior and know how to make captures explicit.
Mark this lesson complete to store local progress and unlock a cleaner resume path the next time you visit.