← All articles

Why I Use Go for CLI Tools

·2 min read
goclideveloper-tools

Go's combination of fast compilation, single-binary output, and excellent standard library makes it the best language I've found for building CLI tools.

After building CLI tools in Python, Node.js, and Rust, I keep coming back to Go. Here's why.

Single binary distribution

When you compile a Go program, you get a single static binary with zero runtime dependencies. Your users don't need to install a runtime, manage virtual environments, or deal with DLL hell. They download a file, chmod +x, and run it.

GOOS=linux GOARCH=amd64 go build -o mytool-linux-amd64 .
GOOS=darwin GOARCH=arm64 go build -o mytool-darwin-arm64 .

That two-liner is your entire cross-compilation story. Compare this to Python, where you're fighting PyInstaller to get a bundled executable, or Node.js where pkg produces 50 MB blobs.

The standard library does most of what you need

Go ships with a remarkably complete standard library for CLI use cases:

  • flag for argument parsing (or reach for cobra/urfave/cli for complex CLIs)
  • os/exec for running subprocesses
  • net/http for talking to APIs
  • encoding/json for JSON
  • text/template for rendering output templates
  • os and filepath for filesystem operations

I can write a useful CLI without a single third-party dependency.

Fast compilation keeps the feedback loop tight

Go compiles quickly. A 10,000-line project builds in under a second. This matters during development — the delay between "save" and "can test this" is near-zero. Compare this to Rust, where incremental builds on a medium-sized project can take 10–30 seconds.

Goroutines make concurrent CLIs trivial

CLIs often need to do multiple things in parallel: check multiple APIs, scan files, process a queue. Go's goroutines make this straightforward without the callback hell of async JavaScript or the complexity of Python's asyncio.

var wg sync.WaitGroup
results := make(chan Result, len(items))

for _, item := range items {
    wg.Add(1)
    go func(i Item) {
        defer wg.Done()
        results <- process(i)
    }(item)
}

wg.Wait()
close(results)

When I don't use Go

Go isn't always the right choice:

  • Rapid prototyping: Python is faster to explore an idea
  • Maximum performance + safety: Rust is worth the compile times
  • Existing ecosystem: If your domain has great Python or Node libraries, use them

But for the majority of production CLI tools I've shipped, Go has been the right call. The combination of fast iteration, easy distribution, and a solid standard library is hard to beat.


What language do you reach for when building CLI tools? I'm curious if others have found a better fit.