Go has seen steady growth over the years, even making it into the top 10 most popular programming languages again in 2023, according to the TIOBE index. But why do more and more companies switch to a new language, when they already have a tried and tested development ecosystem in place? There are multiple answers to that question, which we will break down in this post.
Simple to learn & use
The argument you hear the most when asking what makes go great, is it's simplicity: The syntax is mostly copied from the C language, with some minor additions. You can learn the language in an afternoon, and comfortably write production code after a day. This means onboarding new developers, even if unfamiliar with the language, will take significantly less time than introducing a new team member to an existing project in other languages.
Go on a language-level takes a very minimalistic approach: it provides the base set of tools required, and abstains from adding syntactic sugar or alternatives. Prevention is the only solution to avoiding feature creep: Once you suffer from it, there is no reasonable way back.
One aspect that is less discussed, but not any less important for this argument, is how the language forces code to be "in plain sight": Errors are returned as values, so whether a function can return one is immediately obvious from it's fingerprint. You also cannot accidentally forget to catch an exception, as return values of functions must be assigned to variables (or actively ignored by assigning it to `_`). Go also doesn't have constructors/destructors, decorators, context managers, inheritance or magic functions, so code written in it will be very expressive in nature and rarely have implicit/hidden side effects.
A lot of errors will be caught by the go compiler itself, long before any application gets deployed: Variables or imported packages that are not used in the code are assumed to be errors, static typing prevents type coercion issues, garbage-collection takes care of memory management for you, and interfaces are implicitly implemented by any struct that satisfies them. To make variables, functions or structs available outside of a package, their names have to start with a capital letter, making it immediately obvious whether they are available for use outside of the module, or only internally.
Lastly, the go compiler is extremely fast, compiling changes to large codebases will typically only take a second or two. This allows developers to iterate more quickly: fixing bugs an errors becomes a faster process, as the time spent waiting for the compiler to finish is heavily minimized.
Unlike other languages, the
go command used to compile go source code ships with a variety of tools built-in: static analysis, automated migration to newer APIs, unit testing, code formatting, linters, profiling, documentation, versioning and dependency management are all shipped with it by default. This bundling of developer tools means all developers will have the same streamlined experience when writing software in go, across environments and project lifecycles.
Documentation is mostly derived from the code itself, with only human-readable explanations being required as comments above functions, variables and structs. Documentation is easy to view and navigate using the web interface of the pkgsite tool for private repositories, or it's publicly hosted variant pkg.go.dev for public modules. These tools provide useful, streamlined documentation even for code that has not been actively documented by it's developers at all, while enabling responsible software engineers to provide additional information and guidance on intended usage, deprecation status or warnings.
The standard library is probably one of the most underrated gems of the go ecosystem. It ships with clean implementations for nearly everything a modern systems programming language needs, from basic buffered i/o to a full implementation of the http protocol, robust cryptography and concurrency utilities. Go was built for the modern web and microservice ecosystem, and it shows: Spinning up a web server or making an http request are done in few lines of code without requiring external modules.
Robust versioning & Dependency management
Dependency management can quickly become a nightmare in production code, when version upgrades are necessary due to security issues, performance or stability considerations, or to stay within support lifecycle time windows. Go has opted to handle all of these tasks using a single text file:
go.mod. This file contains the go version the project was written in, so developers can not accidentally try to run it with a version too new/old for it. The file also contains a list of all dependencies imported by the system, including their exact versions, to ensure that each developer can reliably reproduce the same environment.
Handling dependencies is largely automated. If you need a module, simply import it in your code, then run
go mod tidy. Don't need an imported module anymore? Just remove the import line from your code and run
go mod tidy again. Need to upgrade packages to the latest version that does not break compatible with your code? Run
go get -u. It is common to further automate these commands to watch for file changes and do these things in the background, as they are extremely unlikely to cause issues.
Modules are versioned and managed using
git, with their import path corresponding to the location of the git repository containing them. The bleve search engine available at https://github.com/blevesearch/bleve can be imported using
This syntax makes it unambiguous where to find the source code of the package, and also pinpoints the major version to the import, rather than some arbitrary file. This has the added benefit that accidentally upgrading to a newer major version in an existing project is almost impossible, as the
import statements in your code will still import the correct major version.
Go runs on windows, linux and mac, 32 and 64 bit systems and even arm processors. It compiles to native binary executables for the specified target platform and can even be cross-compiled between operating systems, for example allowing to compile a 32 bit windows executable from a 64 bit linux machine.
The final argument in go's favor is it's compatibility with existing software: A lot of code in it's target domain is currently written in C (or support C code through APIs and libraries). Go is able to natively import C libraries using the unsafe package from the standard library. This ability enables it to be useful even in situations where no go library has been written for a specific problem yet, as the C derivative can be used instead. Old code bases written in C can also choose to slowly transition to go using this approach, easing the refactoring load by splitting it into multiple chunks.
As you can see, there are many strong arguments behind go's constant growth in popularity. While go is a great language for the use case it was designed for, namely systems programming, REST APIs and networking, it is not a silver bullet. Trying to build mobile apps, desktop GUIs or games in it will be significantly more effort to do in go than in other languages, and the integration of all the tooling with the
go command itself may lead to poor compatibility with other task runners, linters or unit testing frameworks. As with every programming language, it is a tool for a job and choosing the right tools for your needs is the most important part of software development.