Review on 100 Go Mistakes and How to Avoid Them book

Review on 100 Go Mistakes and How to Avoid Them book

I've been using Golang as my primary development language for over two years. During this time, I've encountered many use cases and challenges with Go, and to be honest, it's my favorite programming language for backend development—especially for cloud-related projects. Go strikes a great balance: it's simple to write and learn, yet close to low-level concepts like pointers. However, diving deeper into Go can be tricky because a lot of its complexities are handled under the hood.

I first learned Golang through the awesome book "Building Web Applications With Golang" by Astaxie. This book is straightforward and covers a lot of ground—not just about Go, but also about building web applications in general. It dives into topics like HTTP servers, database connections, text file handling, and even security concerns. It's incredibly useful if you want to quickly get hands-on experience with Go.

After spending some time developing with Golang, I read "The Go Programming Language" by  Alan Donovan and Brian Kernighan. to dive deeper into Go's concepts. This book is an excellent reference, and I often use it when interviewing candidates for Golang developer positions.

Recently, a friend recommended 100 Go Mistakes and How to Avoid Them to me. As the title suggests, this book covers 100 critical mistakes commonly made by Golang developers due to a lack of deep knowledge about the language. The book is well-organized and easy to read. What I really like is how it shows examples of incorrect code and encourages you to think about better solutions, making it hard to put down!

The book maps its content as a journey, starting with Code and Project Organization, then moving through Data Types, Methods, Concurrency, and Testing, before concluding with Optimization. The author creatively describes this journey as the "Land of Go Mistakes," making the learning experience engaging and memorable.

Land of Go Mistakes from 100 Go Mistakes and How to Avoid Them

The book covers pretty much everything in Golang. I'll share some of my favorite mistakes from the book—ones I’ve made myself and found incredibly useful to learn from.

#3: Misusing init functions

This mistake focuses on the execution order of Go's init functions and highlights where they should and shouldn't be used. For example, consider this which init function will be executed first?

package main

import "fmt"

var a = func() int {
	fmt.Println("var")
	return 0
}()

func init() {
	fmt.Println("init")
}

func main() {
	fmt.Println("main")
}

the execution goes as "var" , "init" and then "main"

What if I create another package and import it into the main package, and both packages have init functions? This one's easy: the init function in the imported package will be executed first, followed by the init function in the main package.

But what if I import multiple packages into the main package? Which one will execute first? In this case, the execution order follows alphabetical order. The key takeaway here is that we should not rely on the init function execution order, as it can lead to confusion and unintended behavior.

Now that we've covered the execution order, let's talk about when to use init functions. Relying on them can limit your error handling because they can't return errors, so your options are essentially to ignore them or panic. They can also complicate testing—if your logic depends on init functions, you might end up importing unnecessary dependencies just for unit tests. Additionally, if your initialization needs state, you’ll likely have to rely on global variables, which, as we know, isn't the best approach.

#23: Not properly checking if a slice is empty

What’s the difference between a nil slice and an empty slice? Let’s take a look at this code to understand it better:

package main

import fmt

func main(){

  var s1 []float32
  s2 := make([]float32, 0)

  fmt.Printf("s1: empty=%t\tnil=%t\n",  len(s1) == 0, s1 == nil)

  fmt.Printf("s2: empty=%t\tnil=%t\n",  len(s2) == 0, s2 == nil)
  
  
}

What will this code print? The output will be empty=true , nil=true for first line and empty=true and nil=false for second line

Sometimes, we might make the mistake of checking a slice’s emptiness using a nil comparison, especially when writing validations for empty inputs. I’ve made this mistake too. To properly check if a slice is empty, it’s better to use the len function. It will return 0 for both empty and nil slices, providing a more reliable way to check for emptiness.

#28: Maps and memory leaks

Let’s consider this case: I create a map, add 1 million key-value pairs, then remove all elements and run the garbage collector. Here’s the code:

package main

func main() {
	n := 1_000_000
	m := make(map[int][128]byte)
	printAlloc()
	for i := 0; i < n; i++ {
		m[i] = randBytes()
	}

	printAlloc()

	for i := 0; i < n; i++ {
		delete(m, i)
	}
	runtime.GC()
	printAlloc()
	runtime.KeepAlive(m)
}

You might think that the memory will be freed, but let’s see what really happens.

from 100 Go Mistakes and How to Avoid Them

As you can see, nearly half of the memory is still allocated. So, what happened? The map allocates buckets for its data, and when you delete items, the buckets remain allocated and won't be freed by the garbage collector. So, what's the solution?

One approach is to create a new copy of the map to reset the buckets, but this can be time- and memory-consuming, especially for large maps.

Another approach is to change the map to something like make(map[int]*[128]byte). While this doesn’t change the fact that the buckets are still allocated, let’s see what the difference is.

from 100 Go Mistakes and How to Avoid Them

The map buckets are not copies of the [128]byte array; they are just pointers. By using this approach, a smaller amount of memory is allocated for them, which helps reduce the overall memory usage compared to larger map entries.

#56: Thinking concurrency is always faster

Many developers make the mistake of thinking that concurrency and using asynchronous approaches will automatically make their code faster. However, this is not always true. The book presents this example to demonstrate why.

Parallel merge sort

Imagine we’re developing a merge sort algorithm and making it concurrent to improve speed. First, we implement a regular, non-concurrent version, then we implement the concurrent version and run Golang benchmarks on both with 10,000 entries.

from 100 Go Mistakes and How to Avoid Them

Surprisingly, the parallel version is much slower than the normal one. The efficiency of concurrent approaches can vary significantly depending on the machine’s CPU configuration. For example, in the case of this merge sort, if you have only one CPU core, it doesn’t matter how many goroutines you use—the code will be executed sequentially. In fact, it might take even more time because the execution order will vary, and you'll need to make more system calls and gather up data.

The book delves deeply into concurrency, as goroutines are Go’s crown jewel. It covers everything from basic parallelism and concurrency to specific use cases and Go’s solutions, such as channels and the sync package. One of my favorite topics is the GOMAXPROCS environment variable.

The GOMAXPROCS variable determines the number of OS threads that handle goroutines. The default value is set to the number of CPU cores on your machine. Go creates a pool of threads based on this value and schedules goroutines across them, placing them in different queues for execution.

The Go runtime creates a local queue for each goroutine and a global queue shared among all of them. Every 1/61 of the time (every sixty-first execution), Go checks the global queue for runnable goroutines. If the global queue is empty, it will check the local queue. If both queues are empty, Go will try to steal a goroutine from another process's queue.

Conclusion

In the end, there are many resources available to learn and improve your Go knowledge. This book is one of my favorites because it’s easy to read and covers both Go’s practical usage and underlying concepts. I think this book is perfect for those who have already worked with Go for a while and are now looking to take their understanding to the next level.

This book has helped me a lot with code reviews and my everyday coding. We even used it as a reference for a Golang junior bootcamp, and the feedback was really positive. Since Go is my favorite language, I’d be happy to hear your thoughts on Golang courses, books, or any other resources.

Thanks for your time, and I hope you enjoyed reading this!

Let me know if you need any more adjustments or additions!