Concurrent API Calls and Race Conditions In Go
A quick example of concurrent programming using Goroutines
A quick example of concurrent programming using Goroutines

Golang (or Go) was first released in 2009 and has been around for some time now.
It’s becoming wildly popular over time. At least that’s what I see from Stack Overflow trends, which is a good measure of how involved our developer community is on a technology/library/framework etc.
Go was developed by Google to help make concurrent programming easier, safer and more efficient. Lewis Fairweather has written an in-depth article on Golang here which summarises the motivation behind Go, the advantages and drawbacks.
In this article, we will examine one of the greatest features of Go: it’s support for concurrency.
We will try to execute multiple API calls and compare its execution time and also understand how simply we can implement concurrency in our programs.
The Base Code
In our program, we will make networks calls to fetch some data. For this purpose, I am going to use the cool Chuck Norris API to get some nice quotes in each call.
First, let’s create the directory for our project and use go mod init <module_path>/concurrent
. Now, let’s create a main.go
file. Let’s define the struct which stores the result from our API call. Here’s the code:
Let’s add a function to make the network call. It looks like the following:
Let’s add an auxiliary function which can print out our execution time since the start of program till completion.
func printExecutionTime(t time.Time) {
fmt.Println("Execution time: ", time.Since(t))
}
This function takes in an argument t
of type time.Time
and calculates the time difference between current time and t
.
Sequential Execution
We are going to fetch a total of 100 quotes. I am going to write a simple function that loops, fetches these quotes, and stores them in a map. The mapping is from the number of the call we are making to the received quote. The code looks like this:
Now, I am going to write my main function, which calls this.
func main() {
startTime := time.Now()
defer printExecutionTime(startTime)
getQuotesSequentially(100)
}
Let’s run this by go run .
in the directory via terminal. This will run all our get quotes sequentially and print out the execution time at the end.
This took a total of 25.52 seconds on my computer! Wow, pretty slow, isn’t it?
Concurrent Execution
Now, let’s make our calls concurrent by using Goroutines and wait groups.
Goroutines are functions that are executed independently and simultaneously along with other Goroutines in the program. Though not exactly the same, it can be visualized as a lightweight thread.
Wait groups are used to help us keep track of the multiple Goroutines we are going to run. This prevents our program from exiting as soon as the main thread completes its execution.
Instead, having a wait group helps us to wait until all the Goroutines are complete fully.
Let’s create a function called GetQuotesConcurrently
to implement this. This would look something like this:
Now, let’s run our main function call the above.
func main() {
startTime := time.Now()
defer printExecutionTime(startTime)
getQuotesConcurrently(100)
}
And running this, we see the following:
Wow, 1.58 seconds! That’s faster than sequential execution by 16 times!
Concurrent Execution (The Right Way)
However, we are not done yet.
Go actually provides a great way for us to check if we have a race condition in our code. Read this article to learn more.
If you run the same function above, but with a -race
flag, go run -race .
to check for race conditions, you would see this:
Note: For clarity, I have removed the fmt.Printf
statement in the function.
We can see that we have made multiple attempts to write to the same map at the same time. This has led to a race condition where multiple goroutines are trying to access the same variable quotesMap
at the same time.
This is not a safe operation and can potentially lead to further issues as our code evolves. To fix this, we will use a sync.Map
, which allows us to synchronize write operations to the map we created.
As described in the documentation:
“Map is like a Go map[interface{}]interface{} but is safe for concurrent use by multiple goroutines without additional locking or coordination. Loads, stores, and deletes run in amortized constant time.”
Let’s update our function to use this.
Now, running our program again with go run -race .
, we don’t see any errors.
Note: For clarity, I have commented out the fmt.Printf
statement in the function.
The Full Code
Here’s the full code we have written so far with all the three main functions:
Conclusion
Yes, it’s that easy to utilize concurrency in Go programs! And the results speak for themselves, especially when we are dealing with a large number of asynchronous operations such as network calls.
Consider using Go’s amazing, simple, and efficient Goroutines to achieve your goals. At the same time, beware of race conditions. Use Go’s built-in race detection tools to check your code.
If you are ready to start creating your own APIs with Go, Ian Duncan has a great tutorial here where you can build a RESTful JSON API.
That’s all folks; happy coding!