Published at Dec 29, 2025
I have recently been writing my own custom SaaS solution and hosting it on AWS.
The architecture is pretty standard. Have an S3 bucket host my React frontend,
with Cloudfront fronting it. For my backend, I’m storing every user data in
DynamoDB, with Lambdas acting as the backend. AWS API Gateway acts as the front
door for my backend, with it relying on Cognito for authentication (yuck). In
the end, it ended up looking pretty good, and I was pretty happy with it. The
entire project is written in TypeScript, and I experienced no cold start
concerns at all. But one day a perverse thought came to my mind, what if I write
the backend in Go. I know, for a Rust developer to think of those thoughts, it
must be sinful. As a person that made a living hating Go, my quarter life crisis
must have changed me for the worse. However, after rewriting my backend in Go, I
don’t actually hate it all too much. Years of programming Java has made me
resistant to most heinous languages. I don’t even mind the gratituous presence
of if err != nil, it’s quaint in a way. However, what I can’t stand is the
presence of zero values, and Go’s insistence if using it instead of returning
nil instead.
In Go, zero values can be anything. There is no official convention on what counts as a zero value (which sometimes act as sentinel values). For example, the zero value of a string could be an empty string. The zero value of an integer could be 0. By convention, these are the most common ones. According to the Go community, it eliminates a whole class of uninitialized variable bugs, and it means you can often use values right away without explicit initialization. A var count int is immediately usable as 0, an empty string is "", and an empty slice is ready to append to. But Go pushes this pattern beyond where it’s naturally comfortable.
In short, they make it easy for you to ignore cases if you do not account for
it. Go makes it a big point to showcase how with nil, ignoring errors is
almost impossible (it is in fact possible). With zero values, ignoring any cases
which can produce zero values requires some effort. This is not some PhD level
skill, but it requires you to lock in and think of every possible behaviour of
the function, especially if they are not well documented.
In Go, if you want to return nil from a function, you need to ensure that the
function returns a pointer. Empty slices vs nil slices is a classic headache.
They’re functionally equivalent in most operations, but not ==, and it’s
unclear which you should prefer. The community generally says “don’t worry about
it,” but that ambiguity itself is a friction point. Structs with zero values can
be misleading too. A time.Time zero value is "0001-01-01", which looks
initialized but probably isn’t meaningful. You have to remember to check
IsZero(). After decoding a JSON with something like this schema:
type JournalRequest struct {
Date string `json:"date"`
Content map[string]any `json:"content"`
} …the only way to know whether date is empty is if it is an empty string. I
know that string cannot be nil, but it is just a consequence of Go’s simple
design.
There are also multiple methods of checking for whether “something’s missing” or
if an operation failed. You can check if an err is not nil, the classic.
This is Go’s primary error handling mechanism. There is also the “ok” idiom,
where a function can return a value, and a boolean stating whether an operation
is successful. If ok is not ok, then the value would most typically be some
zero value, though there’s nothing stopping the library author from having that
value be a corrupted value or a zero value if they are reasonable. There are
also functions that return just a value, where you’re expected to know that the
zero value means “not found” or failure. But this only works when zero isn’t a
valid result. So many C-isms are found in Go’s API surface.
The language tried to be minimal and avoid abstractions like Option types, but
the result is that these patterns are scattered inconsistently across the
standard library. You just have to know which API uses which convention.
func isValidDate(dateStr string) bool {
_, err := time.Parse("2006-01-02", dateStr)
return err == nil
} Didn’t know you have to provide a date as an example so it knows what format to parse 💀. And it must be this unique date too, nothing else works. How the fuck are people supposed to remember this? I understand the reasoning of putting in an example for the options but it’s stupid if it’s a specific date. If you provide any date as an example you would probably incur extra parsing costs to figure out what date format you want anyway.
Despite how much I have voiced my displeasure, I generally had a decent and fun time writing Go. I enjoyed how simple it is to use. After a day of using Go, I am generally confident enough to write what I had wrote in Rust previously in Go, and to that extent it is very useful. It also occupies a very specific but important niche of being a compiled language with a garbage collector. There are many other languages out there which can claim this title (e.g., Nim, Crystal, Haskell, etc.) but Go seemed to be the most popular and mature one out of all of these. Perhaps Swift some day may take the crown, but I generally disliked how much it tried to be everything to everyone at the same time, making Swift slowly develop a reputation for having too many features (e.g., a growing list of over 200 keywords, try to appeal to embedded development, the tooling around SwiftUI, its extremely zealous type inference). I also feel like any language that tries to be also a declarative way to do UI would eventually lead to some complexity. Kotlin developers had to develop many features just for Jetpack Compose to work ergonomically (e.g., property wrappers). That’s why for languages that are not designed for that, the Elm model works very well here, but I digress. All in all, my experience of using Go has been largely positive and I am relieved while also slightly disappointed in myself that my Go hate might have been a bit overblown. I might continue to use Go for my future web projects.