Go (or Golang) is an open-source programming language originally created by Google that has been gaining in popularity due its high performance, expressiveness and readability. In this blog post, I will describe its origins, main features, and the advantages it offers both for programmers and for businesses. Ultimately, I will describe the use cases where it works best.
When Google was growing exponentially, it had to deal with problems of scale. They simply had lots of everything: tens of thousands of engineers, lots of software and systems, millions of lines of code (especially behind their network servers) being developed by hundreds of programmers, and finally lots of hardware on which all this software was launched. To make matters even more complicated, the entire source code was stored in a single monolithic repository. While the code was being built, it was essential to verify if the changes introduced in one place didn’t result in other components not working properly. A huge amount of time was needed to build such a vast codebase. To perform such a build, Google created its own build system to compile the code on many machines in parallel.
Yet, despite this and many other optimization efforts, the time needed to build certain systems reached several dozen minutes in 2007. Given the enormous scale, developing software was a slow and difficult process. Code compilation (mainly in C++) lasted too long. On the other hand, using threads and memory sharing in C++ and Java to write concurrent code was too complicated. Programmers made a lot of mistakes, and the code was often not optimized. To use concurrent programming, a programmer had to create many threads that shared memory of the same process. This was a simple and elegant solution when a program was simple and didn’t contain multiple threads. If there were too many operations waiting for external resources (disk, network, etc.), as was often the case in a typical network application, a thread was left waiting—and gobbling up resources like RAM and CPUs (for context switching). If a server was processing 10,000 parallel connections, the program became very resource-hungry.
To solve this problem, alternative models of concurrent programming were created. An asynchronous model used especially in Node.js is one of them. In this model, when a function is about to perform an I/O operation, it does not block thread, thus making the entire process more effective. Still, it is more complex for programmers to use and much more difficult to debug.
Additionally, there were many approaches to C++ programming, but none emerged as best-in-class. Java faced the same problem. As a result, code written by one programmer was difficult for others to understand. Finally, managing dependencies in a program was problematic. It was particularly hard in C++ to say what functionalities were actually used in the program code, as this language allows you to use a certain functionality without including it explicitly in the code. Consequently, programmers did not actually know what was being used in the program and what could be removed. This resulted in the code being difficult to manage and in needlessly long compilation times.
Fed up with these pitfalls, Googlers Robert Griesemer, Rob Pike and Ken Thompson created a new language that would increase the productivity of Google engineers and allow them to use the company’s huge hardware resources more effectively (CPUs with many cores, a fast network and huge clusters for data processing). Since the most widely used programming languages at Google were C++ (mainly for servers) and Java/Python (the rest of the code), this new language needed to be similar to them, so that programmers could learn it easily. Moreover, many college and university graduates in the US already knew C and Java, so it would also be easy for them to learn.
Golang’s creators sought to create a statically typed compiled language which would be as easy to use as dynamically typed languages and have a similar level of expressiveness (i.e. conciseness as represented by the number of code lines needed to express a given concept). In other words, they wanted to combine C++ with Java and Python while capturing the best features from all of them. As a result, Golang has elements from both statically and dynamically typed languages.
The statically typed language features include:
- type safety—incorrect type use errors are signalled during compilation, rather than when running the code; thus it is possible to avoid part of the problem in the production environment and perform code refactoring safely;
- high performance
The dynamically typed and interpreted language features include:
- the ability to run the code quickly
Not revolutionary, Golang simply combines different features borrowed from other programming languages like Java, Pascal or CSP. It doesn’t introduce any particularly new or revolutionary functionalities à la Rust, but it does take what is best from other languages and uses it for purposes more pragmatic than academic.
Golang syntax and features
Golang syntax was designed to allow virtually everything to be programmed using minimal syntax. And though the language has been on the market for a full ten years, its creators have not introduced any serious syntax modifications in order not to make the code overcomplicated.
This approach stands out from those employed by other mainstream languages, especially C++ and Java. Their syntax has been considerably expanded over the years to keep up with the newest programming trends and introduce features programmers sought to have. As a result, today these languages are completely different from their original versions.
Thanks to the lack of changes in Go, programs being written today in this language differ little if at all from those written a couple of years ago. Go programmers do not have to learn new syntax or new paradigms, as Golang’ syntax is still the same and still as easy to learn as ever.
More importantly still, a simple syntax limits the number of ways code can be written by programmers. As a result, the code authored by different programmers looks alike and is easy to read and understand.
The code can be run quickly. Until now, this was a feature strictly of interpreted languages. Still, Go needs a compiler to compile code to a machine language. Nevertheless, this compilation is very fast, because the language was designed to enable its compiler to analyze dependencies quickly and avoid loading files repeatedly. This is why programs written in C were compiled so slowly. Go, on the other hand, compiles the source code incrementally, creating an object file for every package. When somebody uses a given package, it loads only the previously prepared file. This significantly decreases the number of read operations on the hard drive.
Unit tests can be launched very quickly. Since Golang knows the dependencies of the given package, it needn’t be recompiled, as long as the dependencies haven’t been changed. As Go also caches the test results, there is no need to launch tests for package code that has not been actually recompiled. The test result will be returned from the cache. This allows unit tests to be launched very quickly, as they will include only code that has been changed.
Programs written in Go are executed fast and use little RAM. When Go code is compiled, an executable file is created. This is not a bytecode or any other intermediary code, but a machine code.
Programmers don’t have to worry about garbage. Golang has a built-in garbage collector which removes from the memory instances that are no longer being used. Additionally, the Go compiler uses escape analysis algorithms to decide what memory type (stack or heap) should be allocated to a given structure. A programmer does not have to worry about it. This feature makes programming much easier, while at the same time boosting software performance considerably.
Concurrent programming made easy
Many years ago, when C++ and Java were born, threads were commonly used in concurrent programming. Threads are abstracts allowing a programmer to write concurrent code. Each thread did only one thing at a time. If a programmer wanted a program to perform many functions, they simply created multiple threads, which communicated with each other by sharing the memory inside the process. Access to this memory was synchronized with synchronization primitives. The code in a single thread was executed sequentially. This model was simple and easy to implement.
The rub lay in sharing the memory, which caused multiple problems that gummed up implementation and generated countless bugs that were detected only during production. Additionally, this model was never effective when it comes to using hardware resources. For example, if there were more than 1000 threads, switching threads gobbled up a considerable share of RAM.
Go introduced an alternative model of concurrent programming, one based mainly on CSP language. This new model has two important advantages over the thread and memory sharing model. First, goroutines are used instead of threads. They significantly decrease hardware resource usage. Tens of thousands of goroutines working simultaneously is standard in many Go applications.
Secondly, goroutines communicate with each other using channels that can be described as a mailbox of sorts. One goroutine can send messages to another using the channel. This makes it much easier to understand such code, as there is only one point of communication between the goroutines.
Why use Golang
Go allows you to use different programming paradigms:
- imperative—a programmer gives step-by-step instructions to the machine what actions a program is to perform;
- functional (support for closures)—a programmer can change a programs behavior by providing code as closures;
- procedural—in order not to repeat code, a programmer can create reusable code pieces like procedures and run them multiple times in a program;
- Object-oriented—a more advanced means of reusing code than procedural programming; it uses polymorphism; Go allows you to use only composition instead of inheritance, which is widely seen as an advantage though.
It is the programmer who decides which paradigm to use. For small programs, the procedural approach may be the best, while for more complex systems, object-oriented programming becomes a must.
Golang has excellent team scalability. It can be used both by small teams of programmers and in large enterprises. This is especially useful when you do not know how your team’s composition will change in the future. Python, Ruby and other dynamically typed languages, on the other hand, are an ideal choice for a team of one or two programmers, as these languages provide instant feedback. With code developed for months and years by a much bigger team, using dynamically typed languages effectively becomes a challenge—one Golang is eminently qualified to solve.
You can use Golang both for high- and low-level programming. You can use abstractions and polymorphism. You can also employ proper code refactorization to optimize memory layout for data structures and influence where a compiler will allocate the structure. You can use high-level channels, but when high performance is crucial you can also use mutexes.
Because there is no compiler launched on the program run (JIT), compiled code is foreseeable. This feature makes Golang particularly useful for writing high-performance systems that also contain complex logic. If you want to be clear, you can use polymorphism and encapsulation. In other places, you can use many optimizations. This is the case of database systems, middleware software and infrastructure of any kind. Golang was used in such projects as Kubernetes, Docker, Prometheus or M3.
Golang is a perfect language to code network servers, especially web servers and microservices. Go has functions to support network programming already built into the standard library. You do not need to use frameworks to create simple REST applications (similarly as in the case of NodeJS). In case of more complex ones, you can use advanced HTTP routers (e.g. gorilla/mux).
Another important Golang feature is how its garbage collector works. In Java for many years GC algorithms maximized application throughput (e.g. the number of processed user requests sent to the Internet server, the amount of processed data, etc.). High throughput will result in higher latencies. You can wait longer for garbage to be collected and remove it at one go. This is very effective, but can result in making the application unresponsive for a longer period of time, even for a couple of seconds in the case of large heaps. If the main goal of the application is to process user requests sent to an Internet server, such high latency can take a nasty toll on user experience.
In Golang, on the other hand, the garbage collector is optimized for low latency from the outset. While throughput will be lower, user requests will be processed faster. This makes Golang an ideal solution for Internet servers.
Go and lack of generic types
Of course, Go is not without its drawbacks. While it allows you to use high-level programming, its lack of generic types has come in for criticism. Golang has built-in generic types as arrays, slices and maps, but you cannot define generic types on your own, making the reusing of code difficult. If an application contains similar code located in many places (e.g. this is a CRUD application), an unfortunate result will be multiple lines of repeated code. In the worst-case scenario, you could end up generating application code by yourself. This is not a viable long-term approach.
To solve this problem, the Go team is working on implementing generic types. At the moment, there is still no consensus on how generic types should be supported. Still, first implementations have been created to test different approaches to this challenge.
Golang use cases
Golang is a very good solution for web development, no doubt as it was designed specifically for this purpose. It enables developers to swiftly develop scalable and secure web applications. It also provides enterprises the ability to deploy rapidly on various platforms.
Golang is also used to build and scale cloud computing systems. Docker and Kubernetes are both good examples of where Go has been used in cloud computing. Docker is a platform-as-a-service that allows software to be delivered in containers, while Kubernetes is an open-source container orchestration platform that enables automated application deployment, scaling and management.
Go has also proved very useful for DevOps teams automating tasks and improving the CI/CD process. When it comes to Site Reliability Engineering (SRE), such features as fast build times and lean syntax ensure that a site is both secure and reliable.
Go has typically been used for back-end applications, but it has now started to be used in front-end ones, too. For example, it can be used to create multi-platform apps with GUI. While these applications won’t win a million dollar prize, they clearly show that Go has libraries that can also be used for this purpose.
Golang can be used to create command-line interfaces, especially for DevOps purposes. Good examples are a genial CLI for docker or kubectl for Kubernetes. Additionally, writing complex scripts in Go is much simpler than in Bash.
Finally, through a framework called Gobot, Golang can be employed to program robots, drones and IoT devices. There is also gombile for creating Android applications, but this is still an experimental project.
Golang benefits for programmers and business
From the programmer’s point of view, Go offers a long list of advantages:
- It is easy to learn, concise, expressive and readable
- It offers high performance and the ability to run code quickly
- It signals incorrect type use errors during compilation
- It can be used for high- and low-level programming, and is based on multiple programming paradigms
- It has a built-in garbage collector
Go business benefits are also significant. It can be used to:
- build secure and scalable web applications
- build complex systems requiring high performance
- code network servers, especially web servers and microservices
- build scalable developer teams both in startups and enterprises
- build and scale cloud computing systems