Why Go Language Contributes to Quality Attributes

Luka Boulpaep
Software Engineer
Mitch Van Rechem
Software Engineer

Go programming language makes better software by keeping things simple: everything ships in one package, updates rarely break things, and the tools just work. It handles errors clearly, runs fast, and makes complex code easier to understand. Despite some growing pains around managing external code, Go's sweet spot is building reliable backend systems that last.

Last year at the Golab conference in Florence, engaging with the vibrant Go community inspired us to share our experiences building production systems with this unique programming language. Having used Go to develop and maintain an IoT Platform with over 1.5 million devices for the past 5 years, we've gained valuable insights into how Go's design choices enhance key quality attributes in large-scale systems. It mainly helps us with expanding but also maintaining our platform, which is crucial as we have been developing the project for over a couple of years. Unlike our experience with Node.js services, Go's dependency-free binaries eliminate version update pressures while enabling seamless platform expansion.

This article explores Go's key features, including its focus on simplicity, backward compatibility, built-in tooling, and comprehensive standard library.

a group of men smiling at the viewpoint of Florence in Italy
Our team in Florence last year while attending the Go conference

Why Did Go Become So Popular

Go emerged from Google in 2007 as a response to fundamental challenges in software development: complex codebases, slow build times, and cumbersome dependency management. Born from the frustrations of working with C++ and Java (including a legendary 45-minute build that sparked its creation), Go aimed to streamline development while maintaining performance.

Built with a mission to do more for the working programmer by enabling tooling, automating mundane tasks such as code formatting, and removing obstacles to working on large code bases. By addressing these pain points head-on, Go has enabled teams to focus on delivering value rather than wrestling with tooling and complexity, driving its widespread adoption across industries.

Why We Love Go

  1. Ease of deployment: Go compiles into machine code, bundled as a binary, which can be run anywhere. While JavaScript, an interpreted language, is a popular choice for backend development, it requires a more complex setup because a runtime must be deployed alongside the source code to execute it. The same can be said for Java, which, despite being statically typed and capable of compiling into an intermediate language, still depends on a runtime environment. 
  1. Backwards compatibility: While staying up to date is ideal, breaking changes often make upgrades effort-intensive. With Go’s promise to be backwards compatible, we’re able to upgrade the Go versions without worrying about services breaking. It also allows us to use the newest features straight away without waiting for support from major cloud providers compared to Nodejs.
  1. Simple and easy tooling: Being designed with simplicity in mind, and having uniform linters and formatters built into the language makes code easy to read and simplifies onboarding for newcomers. Not only that, but it makes revisiting years-old services easy as we’re able to quickly understand them. 
  1. Errors as return values: Go's error handling is pretty neat: it avoids throwing exceptions, instead it returns errors as values. While this approach might raise concerns about code clutter due to frequent error checks, it's a design choice that promotes more robust applications. The Go compiler demands that you handle potential errors. Having your function signature show you side effects like errors can occur, is a godsend. In bigger TS or C# code bases, you see the “result pattern” being used, Go just does this by default, by allowing multiple return values, usually including possible errors. This approach ensures that you consciously consider failure scenarios before they occur.
example of how errors can be returned as values - userService.go 
  1. Added performance to Go’s simplicity: An additional benefit of Go's simplicity and maintainability is its solid performance compared to similar garbage-collected languages, such as Node.js, Java, or C# which are languages frequently chosen to build your backend applications in. While Go often outperforms other compiled languages, it is not always that clear cut. A lot of the times Go performs better for highly concurrent loads but if you have single threaded CPU load it performs on par with the other ones. In the context of serverless functions, it often offers faster startup times, and in general it has lower memory and CPU usage, resulting in reduced latency for the same load. 
  1. The beloved Gopher: Developers have embraced Go's iconic mascot, the Gopher, with a lot of love. To the point that we even have plushies laying around at our office! The mere mention of Go often brings this charming character to mind, and it's become more than just a cute face. The Gopher helps create a strong sense of community and identity among Go users. It fosters shared jokes, inspires Gopher-themed artwork, and contributes to the language's approachable tone. This spirit extends to libraries, where developers often create their own Gopher variants to showcase their Go-based projects. For example, the observability system jaeger has its own adorable Gopher. This kind of playful visual connection helps strengthen the feeling of belonging within the Go ecosystem.

Lessons in Building High-Performance Systems With Go

Recently, we had a difficult feature on our hands, where we had to validate and transform incoming data points for storing in a time series fashion. There were a lot of edge cases to identify and resolve. After the first iteration, we had to rewrite some of the existing code base, and Go makes refactoring fun.

This service ingests data from over a million devices that, at best, send state updates every 2 minutes and, at worst, every 30 seconds. So this service needed to be performant. God exists in the space between highly abstracted and interpreted languages and other low-level languages. You have more control over how your data structures are managed in memory through pointers without the overhead of having to allocate and de-allocate every single variable yourself because you still have a garbage collector, which is nice.

  1. Refactoring and the type system

While TypeScript offers robust typing, large-scale refactoring often becomes unwieldy. A typical refactor in our TypeScript services affects 50+ files due to complex type dependencies and test fixtures. In contrast, Go's straightforward type system keeps refactoring focused on business logic. For example, when we recently restructured our device validation pipeline, the changes touched only the core logic files, with the compiler ensuring type safety throughout.

Go's philosophy of "less is more" shines during maintenance. Its standard types and interfaces encourage clear, maintainable code without the complexity of advanced type system features. This makes the codebase accessible to new team members while remaining powerful enough for complex systems.

  1. About interfaces

Interfaces, in combination with structural typing, might be one of our favourite things about Go. A struct defines a dynamic data structure, "what it is"; An interface defines the behaviour of such data structure, "what it does". In Go, as long as a data struct implements all functions defined on an interface, it can be considered that interface, with no extending or inheritance needed.

Go standard library error interface - builtin.go
customErrors.go

This makes interfaces a powerful tool for making your code modular and easily testable, A mock just needs to mimic the behaviour. It helps with the composition of your code, making it for example easy to abstract the store layer in case you change database provider, similar to OOP patterns.

  1. Go’s standard library

The Go standard library is a solid and reliable piece of software. It has everything you need and has been heavily empowered by the inclusion of generics a couple of years ago. Go has come a long way and has matured tremendously. Everything you need to create a solid production-ready back-end API server is included in the “net/http” standard library package. The community generally embraces the philosophy of keeping things bare bones without fancy frameworks. Of course, there are libraries that will try to abstract some things away or offer more helper functions. Still, at some point, these libraries have fallen out of favour after the Go team just added these wanted features to the standard library.  

We had to use the standard library time package heavily in the code we refactored. I never thought that dealing with time, dates, timezones and daylight saving would be enjoyable, but in Go, it is. In comparison, Javascript's time-date package seems sluggish. It has been replaced with a third-party library, "moment.js", in many cases, which seemingly also doesn't seem to be maintained since a year ago at the moment of writing.

  1. Concurrency

Thousands of people have shared the "Go is amazing at concurrency" narrative, a staple in any pro-Go article. However, it indeed has a lot of built-in tools to make concurrency simple and easy to deal with errors coming from concurrent processes. In our case, this service significantly reduces the startup times of our serverless functions by concurrently handling initialisations and the overall latency by making multiple database calls simultaneously. Although this is not exclusive to Go, it offers the most intuitive way to do it.

It Wasn’t Always Sunny—Let’s Talk About Go’s Cloudy Moments

Backwards compatibility, fighting the trade-offs

The Go team's commitment to maintaining backward compatibility comes in handy, yet it can sometimes pose constraints when implementing new features for the language. This dedication requires extra thought behind decisions, occasionally leading to features that, in hindsight, the team acknowledges could have been designed differently. Finding a balance  between innovation and backward compatibility inevitably brings trade-offs.

We have mentioned the inclusion of generics in the language for a couple of years, more precisely since version 1.18, but this didn't come easily. Rob Pike, one of the co-founders of Go, elaborates on this during his talk "What We Got Right, What We Got Wrong" at GoperCon Australia. He explains that attributing to decisions made in the past surrounding interfaces, they took a long time and a couple of iterations to get generics right while staying backwards compatible. 

Package management still has space for improvement

Go still requires some improvements when it comes to package management, more specifically the package registry. Currently packages, from third parties are imported based on a URL often to a source control platform like github. The way of importing dependencies is clear and straightforward. However, how the package registry works is dubious.

go.mod file describing dependencies similar to package.json in node
go.sum file describing all checksums of the required dependencies

Packages are fetched behind the scenes from a Go Proxy server hosted by the Go team. When a package is added to a project, it will check the Go Proxy server, which acts as a cache for frequently used packages. If no cache is hit in the Go proxy server, your `get` command, used to install modules, will go to the original host server to download the modules. Often-used packages are usually available on the proxy server and will get cache hits; less-used packages will not. It means it can't be guaranteed that a package is safe from malicious code or has no vulnerabilities. It only prevents popular packages from removing versions or throwing tags around. Go does not yet automatically audit packages in their proxy either. If you stumble upon unsafe packages, only a conveniently given e-mail address is provided. 

Public Opinion on Go, And Why We Deem it as Our Ideal Choice

Many critics on forums argue that Go is slower than other languages or creates large binaries. However, Go was never intended to be the fastest language or produce the smallest possible binaries.

Go emerged from frustrations with existing languages, where developers had to choose between efficient compilation, execution, or programming ease. The language's design prioritised quick build times to build simple applications in milliseconds and larger applications in seconds while maintaining the benefits of statically typed, compiled languages.

Go achieves an excellent balance between these three priorities. While it may not perfectly suit projects with strict size constraints like embedded development, it excels in back-end and cloud development. When comparing a Go binary to a JavaScript bundle (a common choice in this field), the decision becomes clear: Go's binaries are significantly smaller and enable faster deployments. 

Trade-offs are necessary to strike this balance. If these trade-offs significantly impact your project, Go might not be the right tool—perfectly fine. In our case, Go is our go-to (pun intended) choice whenever we can use it without client restrictions.

Much has been said about how Go helps enhance key quality attributes in software development. But before we wrap up, let’s take a step back and reflect on the most important takeaways from this discussion on Go:

Ultimately, Go strikes a unique balance between simplicity, performance, and maintainability. Whether you're building scalable cloud services, backend systems, or CLI tools, its design choices help teams ship reliable software with confidence. As the ecosystem grows, Go continues to prove itself as a pragmatic choice for modern software development.

Stay ahead
of the game.

Sign up for our monthly newsletter and stay updated on trends, events and inspiring cases.