Worker Pool in Golang

Tags: #advance #topic #golang #goroutine #channels #workerpool #threadpool

Often we end up with some work which is so time-consuming that if we’re able to assign, multiple person/worker, to do that job the execution time will reduce the time which will save a lot of time for those particular tasks.

Today we’re going to solve this problem by creating a worker pool also known as thread pool so that tasks are done by multiple workers concurrently. We’re particularly using Golang’s lightweight thread also known as Goroutine & Channels.

Prerequisites: Goroutine, Channels


A goroutine is a lightweight thread managed by the Go runtime unlike other languages like Python who’s threads are managed by OS and also expensive to run. So goroutines are functions or methods that run concurrently with other functions or methods.


Channels are ways in which different goroutines communicate with each other. We can understand them as pipes through which you can connect with different concurrent goroutines. The communication is bidirectional by default, meaning that you can send and receive values from the same channel.

Let’s define some workers so that we can solve the time issue using goroutines and channels.


func task() {
    time.Sleep(time.Second) // some task to be executed


Note: Each job takes 1 second to complete

func job(workerID, jobID int) {
    fmt.Println("worker", workerID, "started  job", jobID)
    fmt.Println("worker", workerID, "finished job", jobID)


func worker(workerID int, jobs <-chan int, results chan<- int) {
    for j := range jobs {
        job(workerID, j)
        results<- j * 2

In Golang, we define a channel with the keyword `chan`. Anyone get confused when seeing those arrow signs with channels, let’s simplified those first…

chan   // read-write
<-chan // read-only
chan<- // write-only

So we can say without any arrow in the `chan` keyword would mean the channel can read-write which is the default behavior, but if we want a read-only channel we put an arrow sign before the `chan` keyword like `<-chan` and if we want a write-only channel we put an arrow sign after the `chan` keyword like `chan<-` this.

So for our example above the `jobs` channel only reads and the `results` channel only writes data.

So let’s continue on our worker pool example…

Our worker function will receive the jobs and send the results of the job in the results channel.

We make the job function to execute the task function to simulate an actual task running by the worker.

In the `task` function we put a sleep function which will wait for a second so that it behaves like expensive/time-consuming work.

Create a int channel with buffer

func makeIntChannels(buffer int) chan int {
    channel := make(chan int, buffer)
    return channel

Worker pool

func execUsingWorkerPool(numOfJobs, numOfWorkers int) {
    defer duration(track("time using worker pool"))

    jobs := makeIntChannels(numOfJobs)
    results := makeIntChannels(numOfJobs)

    for w := 1; w <= numOfWorkers; w++ {
        go worker(w, jobs, results)

    for job := 1; job <= numOfJobs; job++ {
        jobs<- job

    close(jobs) // closing the job channel to indicate that's all the work we have.

    for i := 1; i <= numOfJobs; i++ {

Without worker pool

func execWithoutUsingWorkerPool(numOfJobs, worker int) {
    defer duration(track("time without using worker pool"))

    for j := 1; j <= numOfJobs; j++ {
        job(worker, j)

Calculate execution time

func track(msg string) (string, time.Time) {
    return msg, time.Now()

func duration(msg string, start time.Time) {
    log.Printf("%v: %v\n", msg, time.Since(start))

whoo!!! lots of code right…
Let’s go through the `main` function to understand what’s happening

Main function

func main() {
    const numOfJobs = 5
    const numOfWorkers = 3

    execUsingWorkerPool(numOfJobs, numOfWorkers)

    execWithoutUsingWorkerPool(numOfJobs, 1)

In the main function, we’re defining the number of jobs and workers as a const a value so that we can reuse them in the worker pool function and single worker pool function.
Let’s check out the `execUsingWorkerPool` function to understand what’s happening.

defer duration(track("time using worker pool"))

In the first line, we use the `defer` keyword, which means that when `execUsingWorkerPool` function executes all other statements in the function block & the last command will be executed would be defined in the `defer` statement, cool right…

`duration` & `track` function here is a util function which allows us to track the execution time. `track` function passed as a parameter in the duration function as in the Golang, this is called higher-order function or first-class citizen which means is a function can be assigned to a variable, pass as a parameter to other function and return a function from another function.

jobs := makeIntChannels(numOfJobs)
results := makeIntChannels(numOfJobs)

Next line we define two int buffer channels as jobs & results. In order to use our pool of workers, we need to send them jobs and collect their results.

for worker := 1; worker <= numOfWorkers; worker++ {
    go worker(worker, jobs, results)

Next line This starts up workers, for our example, we use 3 workers, initially blocked because there are no jobs yet.

for job := 1; job <= numOfJobs; job++ {
    jobs<- job

close(jobs) // closing the job channel to indicate that's all the work we have.

Next, we send a total of 5 jobs and then close the jobs channel to indicate, that’s all the work we have right now.

for i := 1; i <= numOfJobs; i++ {

Finally, we collect all the results of the jobs we define. This also ensures that the worker goroutines have finished all the workers.

If you like, you can read the same article on my Personal Blog

You can read our other blog-posts Here

You can read my other blog-posts Here


worker 3 started  job 1
worker 1 started  job 2
worker 2 started  job 3
worker 2 finished job 3
worker 2 started  job 4
worker 3 finished job 1
worker 3 started  job 5
worker 1 finished job 2
worker 3 finished job 5
worker 2 finished job 4
2021/03/18 09:25:25 time using worker pool: 2.000943787s
worker 1 started  job 1
worker 1 finished job 1
worker 1 started  job 2
worker 1 finished job 2
worker 1 started  job 3
worker 1 finished job 3
worker 1 started  job 4
worker 1 finished job 4
worker 1 started  job 5
worker 1 finished job 5
2021/03/18 09:25:30 time without using worker pool: 5.001234313s

In Conclusion, we can say using the worker pool, execution time reduces to 2+ seconds where without worker pool, it’s taking 5+ seconds. Hopefully, After this, we understand what is a worker pool and how to create one in Golang, and the benefit of using a worker pool.

Why Golang?

When we start learning a new language, we try to find the purpose of the language first so that we can decide if we’re going to use that language in our project or not.

So when starting with Go, it’s common that you’ll hear that **” it’s a systems language”** as this was one of the earlier descriptions of language by the Go team. It was constructed to combine the speed and power of languages like C with the syntactical elegance of modern interpreted languages like Python in mind.

From Go FAQ,

Why Go was created?

Go was born out of frustration with existing languages and environments for systems programming.

Go hasn’t been considered a web language until recently as its main initial purpose to handle system-related tasks.

Now Go is web-ready out of the box, it lacks a lot of the critical frameworks and tools people so often take for granted with web development now. As the community around Go grew, the scaffolding began to manifest in a lot of new and exciting ways. Combined with existing ancillary tools, Go is now a wholly viable option for end-to-end web development.

Back to that primary question:

Why Go?

An honest answer to the above question, it’s not right for every web project, but any application that can benefit from high-performance, scalable as the business grows, secure web-serving out of the box with the added benefits of a beautiful concurrency model would make for a good candidate.

We can summarize Golang’s effectiveness in six points:

1. Concurrency
2. Scalability
3. Error Checking
4. Compiled Language
5. Garbage Collection
6. Cross-Platform


Millions of Platform Users

Go has many built-in features designed to handle several concurrent web requests due to which it is very efficient as compared to other legacy languages such as python etc


Grow with the Business

As an enterprise grows the programs used will be required to do several things at the same time. Golang can easily scale due to its ability to handle several simultaneous tasks.

Error Checking

Nil/Null Malfunction

Go comes with a built-in error type. It uses error values to indicate an abnormal state while writing the code. The developer can spot errors leading to nil/null malfunction.

Compiled Language

Fast Performance

As a compiled language runs source code files through a compiler, which reads source code and generates a binary, or executable, file that is used to run the program. Examples of other popular compiled languages include C, C++, and Swift. This way performance is way faster than other languages like python etc.

If you like, you can read the same article on my Personal Blog

You can read my other blog-posts Here

Garbage Collection

Boost App Speed

Golang’s collection pauses are as low as 100 microseconds. As a result, it is predictable, better performance, and fast loading time.


Low Investment

Golang performs well across various platforms such as Windows, Linux, Unix, Android, IOS, and other operating systems as well as cloud applications. This means businesses don’t have to spend much on ensuring cross-platform functionality.

Getting TestCase Based I/O Without Any Loop In Golang

Sometimes interview questions come with some twist like

Solve this problem without using any loops

So today I will explain how to solve general input/output (i/o) with test cases problem where special rule defines as saying “Do not use any Loop statement”. We’re going to solve this in Golang. When this type of problem was asked by the interviewer, the first thing that came to our mind(especially those who didn’t solve lots of problem-solving in his/her school/university life). The problem statement is given below that we will try to solve the problem without using any loop.

Note: This problem can be solved differently or different recursive implementation can be applied too.

Problem Statement

– We want you to calculate the sum of squares of given integers, excluding any negatives.
– The first line of the input will be an integer N (1 <= N <= 100), indicating the number of test cases to follow.
– Each of the test cases will consist of a line with an integer X (0 < X <= 100), followed by another line consisting of X number of space-separated integers Yn(-100 <= Yn<= 100).
– For each test case, calculate the sum of squares of the integers, excluding any negatives, and print the calculated sum in the output.

Note 1: Do not put blank lines between test case solutions.
Note 2: Take input from standard input and output to standard output.


– Do not use any Loop statement
– You may only use standard library packages
– There should be no output until all the input has been received.


Defining Main & standard library packages

package main

import "fmt"

Defining a global variable for storing results

var result []int

Defining main function

func main() {
    var testCaseNum int

    nTestCase(0, testCaseNum)

Recursively taking inputs using ReadN

Note: The function will finish when the test case and index value become equal and we’re printing the result array recursively using PrintN as Golang doesn’t have any built-in functions which print array without any loop (to my knowledge at least).

func nTestCase(idx int, testCase int) {
    if idx == testCase {
        PrintN(result, 0)
    var n int
    all := make([]int, n)
    ReadN(all, 0, n)  // reading array elements

    result = append(result, sumOfSquare(all, 0, n))

    nTestCase(idx + 1, testCase)

Calculating sum of squares of given integers, excluding any negatives.

func sumOfSquare(values []int, idx int, arrayLen int) int {
    if idx == arrayLen {
        return 0

    if values[idx] > 0 {  // ignoring negative numbers in the array
        return values[idx] * values[idx] + sumOfSquare(values, idx + 1, arrayLen)
    return sumOfSquare(values, idx + 1, arrayLen)

Reading integer array inputs recursively

func ReadN(all []int, idx, n int) {
    if n == 0 {
    if m, err := fmt.Scan(&all[idx]); m != 1 {
    ReadN(all, idx+1, n-1)

Outputting result integer array recursively

func PrintN(values []int, idx int) {
    if len(values) == idx {

    PrintN(values, idx + 1)

In conclusion, When we saw this type of problem for the first time, we thought it’s easy problem until we read the line Do not use any Loop statement in your solution and the problem becomes hard for newbie or not too much experience on problem-solving.

You can read our other blog posts Here

So now we all know how to tackle this type of problem. Stay tuned for our next blog post. Thank you.

Reverse a linked list using Golang

As we start coding and slowly learning about Data Structures(DS), we come across a very famous linear data structure which is known as a linked list. Linked lists and their related questions are quite popular in interviewers who love problem-solving.

What is a Linked List?

A linked list is a common linear data structure. its elements are also known as **Node** are not stored at a contiguous location. The nodes are linked using pointers. Linked lists are popular as their size is not fixed like an array. Linked list’s each node contains a value and a pointer to the next node. The head pointer points to the first node, and the last node of the list points to null. When the linked list is empty, the head pointer points to null.

Types of Linked Lists:
1. Singly Linked List (Uni-directional)
2. Doubly Linked List (Bi-directional)
3. Circular Linked List

I’ll write about linked list types in a different post as this post about reversing a linked list.


  1. Dynamically increase in size
  2. Easy to insertion/deletion


  1. Accessing a random node is not allowed.
  2. Additional memory space needs for each node in a linked list.
  3. Not cache friendly.

Linked List

Node for a linked list

type Node struct {
    prev *Node
    next *Node
    key interface{}

type LinkedList struct {
    head *Node
    tail *Node

Push method for a Linked List

func (ll *LinkedList) Push(key interface{}) {
    list := &Node{
        next: ll.head,
        key: key,
    if ll.head != nil {
        ll.head.prev = list
    ll.head = list
    l := ll.head
    for != nil {
        l =
    ll.tail = l

Display a Linked list

func (ll *LinkedList) Display() {
    list := ll.head
    for list != nil {
        fmt.Printf("%+v ->", list.key)
        list =

// normal display function
func Display(list *Node) {
    for list != nil {
        fmt.Printf("%v ->", list.key)
        list =

Reverse a Linked list

func (ll *LinkedList) Reverse() {
    currentNode := ll.head
    var next *Node
    var previousNode *Node
    ll.tail = ll.head

    for currentNode != nil {
        next, =, previousNode
        previousNode, currentNode = currentNode, next
    ll.head = previousNode

If you like, you can read the same article on my Personal Blog

Main function

func main() {
    link := LinkedList{}

    fmt.Printf("Head: %v\n", link.head.key)
    fmt.Printf("Tail: %v\n", link.tail.key)
    fmt.Printf("head: %v\n", link.head.key)
    fmt.Printf("tail: %v\n", link.tail.key)

// output
Head: 3
Tail: 9
3 -> 1 -> 8 -> 15 -> 12 -> 9 -> 
9 -> 12 -> 15 -> 8 -> 1 -> 3 -> 
head: 9
tail: 3

Installing Multiple Version of Golang using GoEnv

Often we need a different version of go according to specific projects. There are different options we have like we can use Docker for our specific project’s need(we can talk about that in a different blog post). There are several other options but in this blog, we will talk about goenv.

Prerequisites: git

We’re using Ubuntu 20.04 so below instruction will work in ubuntu. On Mac too. Not sure about windows.

Official Installation Guide GOENV Install guide

Goto github

git clone ~/.goenv

if this particular git code syntax you(readers) are not familiar with we will explain it, it just cloning the repo and place it on .goenv folder on the home// directory.

Define environment variable GOENV_ROOT

echo 'export GOENV_ROOT="$HOME/.goenv"' >> ~/.bashrc
echo 'export PATH="$GOENV_ROOT/bin:$PATH"' >> ~/.bashrc

for zsh/oh-my-zsh users, use zshrc or respective config files according to your terminal settings.

Add goenv init to your shell

echo 'eval "$(goenv init -)"' >> ~/.bashrc

If you want goenv to manage GOPATH and GOROOT (recommended), add GOPATH and GOROOT to your shell after eval "$(goenv init -)"

echo 'export PATH="$GOROOT/bin:$PATH"' >> ~/.bashrc

echo 'export GOPATH="$HOME/<workspaces_path>/go"' >> ~/.bashrc

GOPATH Folder Structure

Restart your shell so the path changes take effect.

exec $SHELL

Install Go versions into $GOENV_ROOT/versions

goenv install <version_number>

# check all version which can be installed
goenv install -l

# Example
goenv install 1.15.6

To upgrade to a specific release of goenv

cd $(goenv root)
git pull

Uninstalling goenv

rm -rf goenv root

Disable goenv

To disable goenv managing your Go versions, simply remove the goenv init line from your shell startup configuration. This will remove goenv shims directory from PATH, and future invocations like goenv will execute the system Go version, as before goenv.

Uninstalling Go Versions

goenv uninstall

Run this command after you install a new version of Go to activate

goenv rehash

If you like you can read the same article in my personal github gist

How to use a specified version of Go (Globally or Locally)

# global
goenv global 1.15.6

# Local
goenv local 1.15.6

loops in Golang

Every programming language has some kind of loop for repeatedly executing a block of code. Programming languages like C/C++, C#, Python, Nodejs etc have their own way of writing loops. Among different types of loops the most common one is the for loop which almost all programming languages implement.

Example of a "for" loop

type Node struct {
    Next  *Node
    Value interface{}
var first *Node

visited := make(map[*Node]bool)
for n := first; n != nil; n = n.Next {
    if visited[n] {
        fmt.Println("cycle detected")
    visited[n] = true

In the above example we traverse a linked list of Nodes and show it’s value, along the way we also detect if any cyclic relation present in the linked list.

As other programming languages implement different types of loops like while, for, foreach, do…while etc. On the other hand golang takes a different approach writing different types of loops using a single keyword for which we can say is a universal "for" loop.

Why universal, you may ask?

As other programming languages implement different types of loops, golang makes it’s "for" loop act as all(almost) types of loop.

Different types of loop in golang

1. Basic "for" loop or Three component loop

for init; condition; post_statement {
     loop body

This is the basic way of writing a for loop in every programming language.

// Golang version
total := 0
for i := 1; i < 5; i++ {
    total += i
fmt.Println(total) // (1+2+3+4) = 10

Summary of the above code:
we initialize the total variable to be 0. Then we initialize the for loop by saying – In the init statement we initialize the local variable i to 0 which scope is limited to the for loop.

  • We put a condition statement. i < 5, which will be checked every time the loop starts again.
    • if true, the loops body code will be executed
    • if false, the loops done with the execution.
  • The post statement runs after the condition, and the loop body is executed. in the above code example it's incremented by 1.
  • Back to step 2

2. "foreach" loop

for index, value := range array {
     fmt.Println(`%v %v`, index, value)

Many languages give foreach to traverse or loop over elements of an array of items, maps etc. In Golang foreach loop can be easily achieved by using a for loop.

// Golang version
slice = []int{1,2,3} // array without a limit called slice
for i, v := range slice {
    fmt.Println(i, v)
// Output
0 1
1 2
2 3

3. "While" loop

while condition {
     i++ // i will be used to break from the loop

Generally, we use for loops when we know how many times the loop should run. On the other hand if we need to run a loop to fulfil a certain condition then we can use while loop. In golang we also use the "for" to write a while loop as golang doesn't provide a while keyword for writing it.we write it like the for loop but ignoring the init and post_statement part of a three component loop.

// Golang version
i := 1
for i < 5 {
    i *= 2
fmt.Println(i) // (1*2*2*2) = 8

Summary of the above code:
we initialize the i variable to be 1. Then we initialize the "for" loop

  1. We check the condition statement. i < 5, which will be checked every time the loop starts again.
    • if true, the loops body code will be executed
    • if false, the loop is done with the execution.
  2. Back to step 1

4. "Infinite" loop

In the coding world we sometimes need to write an infinite loop. In other programming languages we use the while loop to write this.

n := 1
while true {
     fmt.Println(n) // this will repeat forever

For golang we also achieve this by writing a for loop.

// Golang version
i := 1
for {
    i++ // this will repeat forever
fmt.Println(i) // this line will never be executed.

we just ignore the condition part of a while loop to make the loop statement an infinite loop.Also note that if we want to break from an infinite loop we need to put a base condition inside the infinite loop.

// Golang version
i := 1
for {
    if i > 10 {
        break // will break the infinite loop when i value reach 10
fmt.Println(i) // 11

5. "do…while" loop

The basic idea of a "do…while" loop is to run a block of code at least one time then check the condition

  • if true, the loops body code will be executed.
  • if false, the loops done with the execution.
do {
     i++ // i will be used to break from the loop
} while(i < 10)

So golang we can write the above scenario in two ways

// 1st way
for ok := true; ok; ok = condition {
// 2nd way
for {
    if !condition {

In summary a "do…while" loop, the condition will be checked after the code block runs at least once.

This post also published as github gist.


As other programming language gives different keywords and syntax for writing loops, golang takes a slightly different approach and make it's for loop act as other loops. So now we can conclude our post by saying "Golangs for loop, can act as universal loop."