How to Make Pure Functions in Golang
Pure functions are often hyped up in the JavaScript world, probably because of the abundance of stateful front end applications. While pure functions have their downsides (i.e. inconvenience, potentially large argument lists), they should be used as much as reasonably possible.
What is a Pure Function?
According to Wikipedia, a Pure function has the following properties:
- Its return value is the always same for the same arguments
- Its evaluation has no side effects (no mutation of data outside the function)
Which means that as a developer I know two important things:
- When I call a pure function I will get the same result every time
- After calling a pure function the rest of my program will be in the same state it was before calling it
Because of these properties, pure functions keep applications simple. As we know, simple applications tend to be faster, are easier to test and debug, and are less error prone in general.
Example in Go
Let’s take a look at an example function. Using Go, we’ll write a countNamesInText function that splits a given string into words delimited by whitespace, then, counts all the words that match a name pulled from the database.
totalCounted := map[string]int{}
func countNamesInText(text string) {
total := 0
name := getNameFromDatabase()
for _, word := range strings.Split(text, " ") {
if word == name {
total++
}
}
totalCounted[name] = total
}
This function is impure for a couple reasons. Let’s examine each one.
1. Program state is mutated by calling countNamesInText()
Instead of mutating a global variable as a means of “returning” data to the caller, we should return the data via a return statement:
func countNamesInText(text string) int {
totalCounted := 0
name := getNameFromDatabase()
for _, word := range strings.Split(text, " ") {
if word == name {
totalCounted++
}
}
return totalCounted
}
Now countNamesInText is more “pure” because it will not change the application’s state, though you may have noticed that we still have another problem.
2. Database Argument
countNamesInText is still impure because the “name” value, which affects the result of the function call, is retrieved from a database. In order for our function to be self-contained, that value should instead be passed as a parameter.
Currently, if we wrote the test:
func TestCountNamesInText(t *testing.T) {
actual := countNamesInText("this word here")
if actual != 2{
t.Errorf("want 2 got %v", actual)
}
}
It wouldn’t work consistently. If the database isn’t set up, or if the database was tampered with, our tests will fail. That makes this a bad test, and we wrote the bad test because we have an impure function.
Let’s purify a bit more:
func countNamesInText(text, name string) int {
totalCounted := 0
for _, word := range strings.Split(text, " ") {
if word == name {
totalCounted++
}
}
return totalCounted
}
Our function is pure, so we can write a good test:
func TestCountNamesInText(t *testing.T) {
actual := countNamesInText("this word here", "this")
if actual != 1{
t.Errorf("want 1 got %v", actual)
}
}
This is what a call to the function in the application might look like:
totalCounted := map[string]int{}
name := getNameFromDatabase()
totalCounted[name] = countNamesInText("some name in here", name)
Related Articles
Create a Golang Video Streaming Server Using HLS
Sep 04, 2020 by Lane Wagner - Boot.dev co-founder and backend engineer
In this tutorial, we’ll go step-by-step through building a video streaming API (which will work for music as well) in Go. Don’t worry, it’s surprisingly easy to build a robust media streaming server, especially if we utilize a modern communication protocol, HLS.
Should You Return Empty or Nil Slices in Go?
Sep 03, 2020 by Lane Wagner - Boot.dev co-founder and backend engineer
In Go, we often need to return zero values. Idiomatic Go encourages the use of guard clauses, and guard clauses necessitate the need to return early. When returning early with an error, by convention all other return values should be zero values. The confusion arises with data types like maps and slices. Should maps and slices be returned as a simple nil value, or should an empty but instantiated value be returned?
Top 15 Golang Interview Questions [Updated 2026]
Aug 31, 2020 by Lane Wagner - Boot.dev co-founder and backend engineer
Let’s take a look at some good technical questions to be familiar with, whether you are looking to nail your next Golang interview, or if you’re the interviewer yourself..
Unexpected Printf Behavior in Go WASM - Nothing Prints
Aug 10, 2020 by Lane Wagner - Boot.dev co-founder and backend engineer
While working on boot.dev’s Go Playground, I came across a very strange error. The standard library’s fmt.Printf() function prints nothing to the console when called. Nothing.