Before any syntax, the useful question is: why pick Go at all? You already know a language or two. Go is worth learning not because it is clever — it is the opposite of clever — but because of what that un-cleverness buys a team building backend services that must run and be maintained for years. This lesson makes that case honestly, trade-offs included, so you know when Go is the right tool and when it is not.
Three Reasons Go Fits the Backend
Go was designed at Google for exactly this problem: large teams, large codebases, services that must be fast to build, deploy and operate. Three design choices follow from that.
1. Simple on purpose. Go is a small language — roughly 25 keywords, a handful of built-in types, one looping construct (for). There is usually one obvious way to do a thing, which means the code your colleague wrote at 2 AM reads like the code you would have written. There is no deep inheritance hierarchy to trace, no operator overloading hiding behaviour, no three competing frameworks for the same job inside one codebase. A new engineer becomes productive in days, not months, because there is simply less language to learn before you can read the codebase. For a team, this readability and uniformity is worth more than any individual feature.
2. Fast to run and deploy. go build produces a single static binary — the compiled program with everything it needs linked in. There is no interpreter to install on the server, no runtime version to match, no “works on my machine” over a mismatched dependency. You copy one file and run it. It starts in milliseconds (not the multi-second JVM warm-up), and it uses low, predictable memory. For containers this is close to ideal: a tiny image (often FROM scratch plus the binary), fast pulls, quick autoscaling, small attack surface. The simplicity you enjoy while coding follows the program all the way to production.
3. Concurrent by design. This is Go’s headline feature and the reason it dominates in networking and infrastructure (Docker, Kubernetes, etcd, and countless API services are written in it). Concurrency is not a library you import — it is in the language. You start a concurrent task with one keyword, go f(), and you coordinate tasks with channels. The runtime multiplexes these lightweight goroutines onto a few OS threads for you (Lesson 4), so handling tens of thousands of simultaneous connections is routine rather than heroic. We will measure this later — a million goroutines launched and completed in 586 ms — but the point here is that Go treats “do many things at once” as a first-class concern, which is precisely what backend services need.
The Honest Trade-offs
A fair case names the costs. Go buys its simplicity by leaving things out, and some of those omissions will annoy you coming from another language:
- No inheritance / no class hierarchies. You compose behaviour from small pieces instead (Lesson 3). Different mental model; takes a little adjusting.
- No exceptions. You handle errors as ordinary return values, checked right where they happen (Lesson 2). This is more verbose —
if err != nilis everywhere — and newcomers often recoil at it before they appreciate it. - Generics arrived late. For its first decade Go had no generics at all; they landed in 2022 (Go 1.18). The language still uses them sparingly by design.
- It is opinionated.
gofmtformats your code one way, full stop — no debates about braces or tabs. Unused imports and variables are compile errors, not warnings. Some find this strict; most come to love not arguing about it.
Here is the reframe that makes Go click: these omissions are the product, not a shortfall. Less language means less to argue about in code review, less cleverness to decode six months later, and fewer ways for a seemingly small change to surprise the team. Go optimises for the reader and for the long-lived codebase, sometimes at the expense of the writer’s convenience in the moment. For a quick one-off script, that trade might not be worth it — reach for Python. For a service a team will run and evolve for five years, boring-on-purpose is exactly what you want.
Where Go Shines (and Where It Doesn’t)
To set expectations for the rest of the course:
- Great fit: HTTP and gRPC APIs, microservices, network proxies and gateways, CLI tools, infrastructure and DevOps tooling, concurrent data-moving services, anything that must be a small, fast, easily deployed binary handling lots of connections.
- Workable but not its sweet spot: heavy numerical / scientific computing and data science (Python’s ecosystem wins), rich desktop GUIs, and problems that genuinely want a sophisticated type system or deep generics.
For the backend services this course is about, Go sits right in the middle of its sweet spot. The rest of the course is about using it well there — starting, in the next lesson, with the fundamentals that newcomers most often get wrong, above all how Go handles errors.
Pitfalls to Avoid
- Expecting features from your old language. Looking for classes, inheritance, exceptions or try/catch and concluding Go is “missing” things. It left them out on purpose — learn the Go idioms (compose, return errors) rather than fighting the language.
- Dismissing Go as too simple. “It has no generics / no fancy types, so it must be a toy.” The simplicity is a deliberate trade for team-scale maintainability; the busiest infrastructure in the world runs on it.
- Recoiling at
if err != nil. The verbosity looks like noise until you have debugged a production incident and every failure path was visible on the page. Give it a lesson before you judge it (Lesson 2). - Using Go for the wrong job. Picking Go for a data-science notebook or a numerical pipeline because it is “fast,” then fighting a thin ecosystem. Match the tool to the task — Go’s strength is backend services and tooling.
- Ignoring the deployment win. Treating “one static binary” as a footnote. It is one of Go’s biggest practical advantages — lean containers, simple ops, fast startup — and it shapes how you package and run services (Lesson 11).
Quick Recap
- Go is built for backend services a team maintains for years, and three design choices follow: it is simple on purpose, fast to run and deploy (one static binary, millisecond start, low memory), and concurrent by design (goroutines and channels in the language).
- The trade-offs are deliberate: no inheritance, no exceptions, late generics, verbose error handling, strict formatting. Less language means less to argue about and fewer surprises — Go optimises for the reader and the long-lived codebase.
- Sweet spot: APIs, microservices, network and infra tooling, CLIs — small fast binaries handling many connections. Not its strength: heavy numerical/data-science work or rich GUIs.
- Don’t fight the language by expecting your old one’s features; learn the Go way, starting with errors.
Next lesson: the fundamentals that trip up every newcomer — chiefly that in Go, errors are values you check, not exceptions that are thrown.