Learning Go Wasn't Easy

Learning Go wasn't easy for me.

I was two or three years into my first job when the company felt they needed to "mature" their dev stack. We were to abandon my beloved PHP—teams had to choose between Go or C# for all new projects. My team chose Go. The simplicity, the toolchain, and the promise of a quick ramp-up persuaded us.

Going through the "Tour of Go," picking up the syntax was a breeze. I felt confident, and I dove head-first into my first Go project. It didn't take long at all to feel like I was drowning.


Diving into that first Go project, I saw the io.Reader and io.Writer interfaces everywhere in the standard library. I was missing something. I didn't understand what they did or why they existed.

PHP automatically populates a $_REQUEST variable at the start of execution. I never needed to understand conceptually how a request gets from client to server. The request was just a big object I had access to.

The interfaces only clicked once I understood how software sends data over a network: at the end of the day, everything is just a stream of bytes.


Coming from a single-threaded language, Go's concurrency model gave me issues. I had some intuition about synchronization from working with databases, but Go was my first encounter with shared memory in the code I wrote.

On top of my inexperience, the Go community touted channels as a prominent feature. Channels must be the right tool for synchronization, right?

It took a lot of channel misuse before I discovered the rest of the primitives in the sync package. It took even longer to discover one of my favorite Go packages errorgroup.

Fun Example

I vividly remember sitting at my desk, stumped by the first concurrency-related bug I encountered. See if you can spot the bug in the example below!

func HelloHandler() http.HandlerFunc {
	var layout struct {
		Name string `json:"name"`

	return func(w http.ResponseWriter, r *http.Request) {
		err := json.NewDecoder(r.Body).Decode(&layout)
		if err != nil {
			http.Error(w, http.StatusText(400), 400)
		defer r.Body.Close()

		fmt.Fprintf(w, "Hello %s", layout.Name)

Project Structure

By far, the hardest challenge of that first project was the structure. It was a blank slate, and I didn't have the dictates of a framework to fall back on.

How do I organize my code? When should I break something out into a new package? Should my HTTP handler be in their own package or with the business logic? Do I use a /pkg folder?

I spent hours researching and reading blog posts, but at the time, there weren't a ton of Go-specific resources. After much trepidation, I eventually picked up a book on Domain Driven Design and ran with it.

Nowadays, there are a ton more resources I wish I had. Organizing a Go module from the Go team is a great place to start.

Wrapping It Up

I'm sure my experience learning Go isn't unique, especially among young developers coming from other languages.

What I love about Go also makes it difficult to learn. It doesn't hide the details of the underlying technology. It demands you think deeply about how to organize your code.

We do a disservice to the community by pretending that learning any new language is "easy."