AlgoMaster Logo

First-Class Functions

Last Updated: May 22, 2026

Medium Priority
4 min read

In Go, functions are values. You can store them in variables, pass them into other functions, return them from functions, and put them inside data structures like slices and maps. That single property unlocks a lot of design flexibility: a sort routine that takes the comparison rule as an argument, a filter that accepts the test as a parameter, a registry of discount strategies looked up by name.

Functions as Values

Every function in Go has a type, just like every other value. The type is determined by its parameter list and its return list. Two functions with the same signature have the same type, even if their names differ.

The line discount := halfOff assigns the function (not its result) to a variable. Notice there are no parentheses after halfOff. With parentheses you'd be calling it; without them, you're grabbing the function itself. Once discount holds the function, calling discount(40.00) runs halfOff(40.00).

The inferred type of discount is func(float64) float64. That's the function's type signature: one float64 in, one float64 out. Any function with that same signature can be assigned to discount.

Named Function Types

Writing func(float64) float64 everywhere gets noisy fast. Go lets you give a function signature a name with a type alias, the same way you'd name a struct.

Discount is now a named type whose underlying type is func(float64) float64. Function signatures become self-documenting. A parameter declared as Discount reads as "this is a discount strategy" instead of "this is a function that happens to take and return a float64". You'll see this pattern across the standard library: http.HandlerFunc, sort.Interface, and filepath.WalkFunc are all named function types.

Passing Functions as Arguments

Accepting a function as a parameter lets the caller pick the behavior while your code just runs it.

applyDiscount doesn't care how the discount is calculated. It accepts any function with the matching signature and calls it. Want a new discount rule? Write another function, pass it in, done. No conditional ladder, no interface to define, no class hierarchy.

This is exactly how the standard library's sort.Slice works. You pass it a slice and a function that decides which of two elements comes first.

sort.Slice knows how to sort. It doesn't know what "smaller" means for your data. You supply that as a comparator function, and it does the rest. The function arguments you saw here are anonymous functions. For now, the takeaway is that you can write a function inline at the call site and pass it directly.

A filter function follows the same pattern. The caller decides what counts as a match.

filter walks the slice and keeps every product for which keep(p) returns true. Swap inStock for an underTwentyDollars function, or topRated, or inCategory("electronics"), and the same filter handles all of them.

Storing Functions in Data Structures

Because functions are values, you can put them in slices and maps. A map of functions is one of the cleanest ways to dispatch on a string key without writing a long switch.

The map ties each coupon code to the function that applies it. Adding a new code is one line in the map. No switch statement to update, no risk of forgetting a case.

Returning Functions

A function can also return a function. This is the seed of closures. For now, here's the basic shape.

percentOff takes a percentage and returns a function tailored to that percentage. Each call to percentOff produces a fresh function value. The returned function remembers the percent it was created with, which is closure behavior. The piece that matters here is the signature: percentOff returns func(float64) float64, and that returned value is just another function you can call, store, or pass around.

Comparing Function Values

Here's a constraint that surprises people coming from other languages. Function values in Go are not comparable to each other. The only legal comparison is against nil.

The zero value of a function type is nil. Calling a nil function panics with runtime error: invalid memory address or nil pointer dereference, so you check before invoking when there's any chance the value might be unset.

Trying to compare two function values to each other with == is a compile error: invalid operation: fn1 == fn2 (func can only be compared to nil). The reason is that function identity is fuzzy in Go: two function literals with identical bodies aren't guaranteed to be equal, and the compiler can deduplicate or share them across compilations. Forbidding the comparison sidesteps a class of subtle bugs.

Anonymous Functions, Briefly

You've already seen them in the sort.Slice and discount-map examples: a function with no name, defined right where it's used. They pair naturally with first-class functions because most of the time, the function you want to pass is one-off and short.