I like Go.
It is definitely not perfect but there is much to like.
Let me count some things I like:
Simplicity and Correctness Link to heading
Go values simplicity and correctness, both for the language itself and its tooling.
Unlike many other languages where choices of build tools, networking, threading models, memory management, and event loops are up to the user, Go takes the opposite route.
Go has one simple, high-quality way to do things.
Concurrency Model Link to heading
Goroutines and channels allow concurrency in the Go language.
The idea of using goroutines as an asynchronous function might seem simple but it actually is very powerful.
A developer can write simple code that looks like it blocks but it doesn’t.
Go will automatically schedule and run goroutines on a small number of operating system threads. It manages preemption and blocking on I/O or other resources automatically.
Each goroutine starts with a tiny stack size that can grow as needed, so the memory usage of go programs tends to be extremely small. 2 to 3 orders of magnitude smaller memory footprint vs a similar JVM application is common.
Goroutines are the only way to do threading in the go language. All goroutines are equal.
All go functions are one color, the developer does not have to worry about marking functions as async or suspend.
Goroutines and channels have been copied by many other languages from Kotlin Coroutines to Rust’s Tokio library.
Java’s Virtual Threads are an attempt to unify many many different threading models in the JVM and are copying go’s threading model.
High Quality Standard Libraries Link to heading
Go has high quality standard libraries.
One example is the net/http client and server library. This library transparently supports HTTP/1.x and HTTP/2.0. HTTP/3.0 and QUIC are not in the standard library but the quic-go module is easy to use with net/http.
Go is still adding to the standard library after many releases, some recent examples are slog for structured logging and enhancing net/http.ServeMux.
Fast Runtime, Low Memory Usage Link to heading
Go is fast at runtime.
Because it uses ahead of time (AOT) compilation there is no worry for just in time (JIT) compilation slowing things down, or requiring warmups which are common in the JVM world.
Go automatically creates a fixed number of threads based on available CPUs at startup and runs goroutines there.
Go has one garbage collection algorithm with only 2 knobs (GOGC and GOMEMLIMIT), compared with the JVM’s myriad of GC algorithms and options.
As of 2018 (7 years ago) Go’s garbage collector has 2x 500 microsecond pauses per GC cycle. Often the pauses are smaller in the 100-200 microsecond range. Meanwhile in 2025 the default GC pause time of the JVM is 200 milliseconds. Go’s GC has 2-3 orders of magnitude smaller pauses vs the JVM.
Because the JVM’s pause times are big (200 milliseconds) a common tactic even for stateless applications is to use multiple GBs of heap space. 2-4GB might be considered small for high-performance JVM apps so the GC does not have to run often.
With go heap usage in 10s of megabytes is common for a stateless application.
Go Modules Link to heading
Go has something like Gradle built-in to the language. It manages dependency versions, transitive dependencies, and embedding version info to an app automatically.
Go modules do not rely on a central repository for build artifacts like Gradle or Maven. They take a simple approach of checking out code from a repository (e.g. github), building it locally, and caching the compiled library locally.
I find go modules defaults to be more sane than Gradle, for example with just one go build
command I can build my app and produce an executable.
With Gradle applications we must worry which plugin to use (shadowjar? application? spring boot fatjar?) and arcane details like whether a jar’s manifest file contains the correct main class name. For libraries we must worry about complex processes to deploy to private or public maven/gradle repositories. None of this is needed in go.
In just 3 commands and a few milliseconds we can update go itself and all modules to their latest versions for a project, while respecting semver:
go get go@latest
go get -u ./...
go mod tidy
In the JVM world it is not uncommon for updating libraries to take days/weeks/months of effort.
Meanwhile go will automatically download new versions of itself as needed at build-time. We are ensured forward and backward compatibility betwen go versions.
Fast Builds Link to heading
Go’s builds are extremely fast.
Running tests and builing often takes less than 1 second even for large projects.
Meanwhile a JVM app might take many seconds or minutes to build and startup, and we must worry about the costs of JIT when the app starts up.
Go’s compiler is self-hosting. Builds will automatically use all available CPUs.
Go is in fact so fast that Typescript is rewriting their compiler in go for a 10x performance improvement vs the self-hosting compiler.
Cross Compilation Link to heading
As go uses AOT compilation, we have to consider what operating system and cpu architecture we want to run the executable on. By default go build
produces an executable for the local OS and CPU.
Fortunately cross-compiling is built-in we just set 2 environment variables at build-time: GOOS and GOARCH
By setting these variables we can run the build on any platform and produce an executable for any platform go supports (which is quite wide-ranging and includes some more obscure options):
GOOS=linux GOARCH=arm64 go build
GOOS=linux GOARCH=amd64 go build
GOOS=darwin GOARCH=arm64 go build
GOOS=js GOARCH=wasm go build
GOOS=aix GOARCH=ppc64 go build
GOOS=linux GOARCH=s390x go build
Single File Executable Link to heading
Go’s builds produce a single file executable with no runtime dependencies.
The entire go-runtime is statically linked into the executable. Executable sizes less than 10MB are common.
We do not have to worry about installing a runtime, or having runtime dependencies available as in the JVM and many other languages (python/ruby/node/perl/etc)
We can even embed files into the executable, this is useful for HTTP servers with static assets.