Learning Go
Notes from learning Go after working primarily with Node.js.
After years of writing Node.js professionally, I spent a few weeks building small tools in Go. This post is a running list of things that surprised me, annoyed me, and eventually won me over.
Goroutines
Goroutines are Go’s unit of concurrency. They are cheap enough that spawning thousands of them is unremarkable, and the runtime multiplexes them onto a small pool of OS threads.

The mental model is close to async/await, except there is no colored function problem:
any function can block without holding up a thread.
Channels are typed pipes through which goroutines communicate. Do not communicate by sharing memory; share memory by communicating.
Error handling
Errors are values. There are no exceptions to catch — functions return an error and you deal
with it immediately:
func fetch(url string) ([]byte, error) {
resp, err := http.Get(url)
if err != nil {
return nil, fmt.Errorf("fetch %s: %w", url, err)
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("read %s: %w", url, err)
}
return body, nil
}
Verbose? Sometimes. Explicit? Always.
What I like so far
- Single static binaries —
go buildand copy the file. - The standard library covers most of what I used to reach for packages to get.
go test ./...needs no configuration.
And a short list of things I miss from JavaScript:
- The npm ecosystem
- Destructuring everywhere
Array.prototype.map
Comparing defaults
| Concern | Node.js | Go |
|---|---|---|
| Concurrency | Event loop | Goroutines |
| Typing | TypeScript (opt-in) | Static (built-in) |
| Deployment | node_modules |
Static binary |
Overall: still early, but the language spec is surprisingly readable.