Skip to content(if available)orjump to list(if available)

Go is still not good

Go is still not good

596 comments

·August 22, 2025

blixt

I've been using Go more or less in every full-time job I've had since pre-1.0. It's simple for people on the team to pick up the basics, it generally chugs along (I'm rarely worried about updating to latest version of Go), it has most useful things built in, it compiles fast. Concurrency is tricky but if you spend some time with it, it's nice to express data flow in Go. The type system is most of the time very convenient, if sometimes a bit verbose. Just all-around a trusty tool in the belt.

But I can't help but agree with a lot of points in this article. Go was designed by some old-school folks that maybe stuck a bit too hard to their principles, losing sight of the practical conveniences. That said, it's a _feeling_ I have, and maybe Go would be much worse if it had solved all these quirks. To be fair, I see more leniency in fixing quirks in the last few years, like at some point I didn't think we'd ever see generics, or custom iterators, etc.

The points about RAM and portability seem mostly like personal grievances though. If it was better, that would be nice, of course. But the GC in Go is very unlikely to cause issues in most programs even at very large scale, and it's not that hard to debug. And Go runs on most platforms anyone could ever wish to ship their software on.

But yeah the whole error / nil situation still bothers me. I find myself wishing for Result[Ok, Err] and Optional[T] quite often.

xyzzyz

Go was designed by some old-school folks that maybe stuck a bit too hard to their principles, losing sight of the practical conveniences.

I'd say that it's entirely the other way around: they stuck to the practical convenience of solving the problem that they had in front of them, quickly, instead of analyzing the problem from the first principles, and solving the problem correctly (or using a solution that was Not Invented Here).

Go's filesystem API is the perfect example. You need to open files? Great, we'll create

  func Open(name string) (*File, error)
function, you can open files now, done. What if the file name is not valid UTF-8, though? Who cares, hasn't happen to me in the first 5 years I used Go.

stouset

> Who cares, hasn't happen to me in the first 5 years I used Go.

This is the mindset that makes me want to throttle the golang authors.

Golang makes it easy to do the dumb, wrong, incorrect thing that looks like it works 99.7% of the time. How can that be wrong? It works in almost all cases!

The problem is that your code is littered with these situations everywhere. You don’t think to test for them, it’s worked on all the data you fed it so far, and then you run into situations like the GP’s where you lose data because golang didn’t bother to think carefully about some API impedance mismatch, can’t even express it anyway, and just drops things on the floor when it happens.

So now your user has irrecoverably lost data, there’s a bug in your bug tracker, and you and everyone else who uses go has to solve for yet another a stupid footgun that should have been obvious from the start and can never be fixed upstream.

And you, and every other golang programmer, gets a steady and never-ending stream of these type of issues, randomly selected for, for the lifetime of your program. Which one will bite you tomorrow? No idea! But the more and more people who use it, the more data you feed it, the more clients with off-the-beaten-track use-cases, the more and more it happens.

Oops, non-UTF-8 filename. Oops, can’t detect the difference between an empty string in some JSON or a nil one. Oops, handed out a pointer and something got mutated out from under me. Oops, forgot to defer. Oops, maps aren’t thread-safe. Oops, maps don’t have a sane zero value. And on and on and fucking on and it never goddamn ends.

And it could have, if only Rob Pike and co. didn’t just ship literally the first thing they wrote with zero forethought.

blibble

> Golang makes it easy to do the dumb, wrong, incorrect thing that looks like it works 99.7% of the time. How can that be wrong? It works in almost all cases!

my favorite example of this was the go authors refusing to add monotonic time into the standard library because they confidently misunderstood its necessity

(presumably because clocks at google don't ever step)

then after some huge outages (due to leap seconds) they finally added it

now the libraries are a complete a mess because the original clock/time abstractions weren't built with the concept of multiple clocks

and every go program written is littered with terrible bugs due to use of the wrong clock

https://github.com/golang/go/issues/12914 (https://github.com/golang/go/issues/12914#issuecomment-15075... might qualify for the worst comment ever)

jen20

I can count on fewer hands the number of times I've been bitten by such things in over 10 years of professional Go vs bitten just in the last three weeks by half-assed Java.

0x696C6961

Ikr, why didn't they just make zero mistakes?

jerf

While the general question about string encoding is fine, unfortunately in a general-purpose and cross-platform language, a file interface that enforces Unicode correctness is actively broken, in that there are files out in the world it will be unable to interact with. If your language is enforcing that, and it doesn't have a fallback to a bag of bytes, it is broken, you just haven't encountered it. Go is correct on this specific API. I'm not celebrating that fact here, nor do I expect the Go designers are either, but it's still correct.

klodolph

This is one of those things that kind of bugs me about, say, OsStr / OsString in Rust. In theory, it’s a very nice, principled approach to strings (must be UTF-8) and filenames (arbitrary bytes, almost, on Linux & Mac). In practice, the ergonomics around OsStr are horrible. They are missing most of the API that normal strings have… it seems like manipulating them is an afterthought, and it was assumed that people would treat them as opaque (which is wrong).

Go’s more chaotic approach to allow strings to have non-Unicode contents is IMO more ergonomic. You validate that strings are UTF-8 at the place where you care that they are UTF-8. (So I’m agreeing.)

null

[deleted]

herbstein

Much more egregious is the fact that the API allows returning both an error and a valid file handle. That may be documented to not happen. But look at the Read method instead. It will return both errors and a length you need to handle at the same time.

nasretdinov

The Read() method is certainly an exception rather than a rule. The common convention is to return nil value upon encountering an error unless there's real value in returning both, e.g. for a partial read that failed in the end but produced some non-empty result nevertheless. It's a rare occasion, yes, but if you absolutely have to handle this case you can. Otherwise you typically ignore the result if err!=nil. It's a mess, true, but real world is also quite messy unfortunately, and Go acknowledges that

silverwind

> What if the file name is not valid UTF-8, though

They could support passing filename as `string | []byte`. But wait, go does not even have union types.

lblume

But []byte, or a wrapper like Path, is enough, if strings are easily convertible into it. Rust does it that way via the AsRef<T> trait.

koakuma-chan

> What if the file name is not valid UTF-8

Nothing? Neither Go nor the OS require file names to be UTF-8, I believe

zimpenfish

> Nothing?

It breaks. Which is weird because you can create a string which isn't valid UTF-8 (eg "\xbd\xb2\x3d\xbc\x20\xe2\x8c\x98") and print it out with no trouble; you just can't pass it to e.g. `os.Create` or `os.Open`.

(Bash and a variety of other utils will also complain about it being valid UTF-8; neovim won't save a file under that name; etc.)

johncolanduoni

Well, Windows is an odd beast when 8-bit file names are used. If done naively, you can’t express all valid filenames with even broken UTF-8 and non-valid-Unicode filenames cannot be encoded to UTF-8 without loss or some weird convention.

You can do something like WTF-8 (not a misspelling, alas) to make it bidirectional. Rust does this under the hood but doesn’t expose the internal representation.

null

[deleted]

nasretdinov

Note that Go strings can be invalid UTF-8, they dropped panicking on encountering an invalid UTF string before 1.0 I think

xyzzyz

This also epitomizes the issue. What's the point of having `string` type at all, if it doesn't allow you to make any extra assumptions about the contents beyond `[]byte`? The answer is that they planned to make conversion to `string` error out when it's invalid UTF-8, and then assume that `string`s are valid UTF-8, but then it caused problems elsewhere, so they dropped it for immediate practical convenience.

null

[deleted]

ants_everywhere

> they stuck to the practical convenience of solving the problem that they had in front of them, quickly, instead of analyzing the problem from the first principles, and solving the problem correctly (or using a solution that was Not Invented Here).

I've said this before, but much of Go's design looks like it's imitating the C++ style at Google. The comments where I see people saying they like something about Go it's often an idiom that showed up first in the C++ macros or tooling.

I used to check this before I left Google, and I'm sure it's becoming less true over time. But to me it looks like the idea of Go was basically "what if we created a Python-like compiled language that was easier to onboard than C++ but which still had our C++ ergonomics?"

shrubble

Didn’t Go come out of a language that was written for Plan9, thus pre-dating Rob Pike’s work at Google?

BugsJustFindMe

> Go was designed by some old-school folks that maybe stuck a bit too hard to their principles, losing sight of the practical conveniences.

It feels often like the two principles they stuck/stick to are "what makes writing the compiler easier" and "what makes compilation fast". And those are good goals, but they're only barely developer-oriented.

xg15

Not sure it was only that. I remember a lot of "we're not Java" in the discussions around it. I always had the feeling, they were rejecting certain ideas like exceptions and generics more out of principle, than any practical analysis.

Like, yes, those ideas have frequently been driven too far and have led to their own pain points. But people also seem to frequently rediscover that removing them entirety will lead to pain, too.

bojo

I recall that one of the primary reasons they built Go was because of the half-day compile times Google's C++ code was reaching.

zhengyi13

I am reminded when I read "barely developer oriented" that this comes from Google, who run compute and compilers at Ludicrous Scale. It doesn't seem strange that they might optimize (at least in part) for compiler speed and simplicity.

sureshv

What makes compilation fast is a good goal at places with large code bases and build times. Maybe makes less sense in smaller startups with a few 100k LOC.

xtracto

I recently started writing Go for a new job, after 20 years of not touching a compiled language for something serious (I've done DevKitArm dev. as a hobby).

I know it's mostly a matter of tastes, but darn, it feels horrible. And there are no default parameter values, and the error hanling smells bad, and no real stack trace in production. And the "object orientation" syntax, adding some ugly reference to each function. And the pointers...

It took me back to my C/C++ days. Like programming with 25 year old technology from back when I was in university in 1999.

pjmlp

And then people are amazed for it to achieve compile times, compiled languages were already doing on PCs running at 10 MHz within the constraints of 640 KB (TB, TP, Modula-2, Clipper, QB).

rollcat

That's a bit unfair to the modern compilers - there's a lot more standards to adhere to, more (micro)architectures, frontends need to plug into IRs into optimisers into codegen, etc. Some of it is self-inflicted: do you need yet-another 0.01% optimisation? At the cost of maintainability, or even correctness? (Hello, UB.) But most of it is just computers evolving.

But those are not rules. If you're doing stuff for fun, check out QBE <https://c9x.me/compile/> or Plan 9 C <https://plan9.io/sys/doc/comp.html> (which Go was derived from!)

remus

> [some] compiled languages were already doing on PCs running at 10 MHz within the constraints of 640 KB

Many compiled languages are very slow to compile however, especially for large projects, C++ and rust being the usual examples.

bwfan123

> Concurrency is tricky

The go language and its runtime is the only system I know that is able to handle concurrency with multicore cpus seamlessly within the language, using the CSP-like (goroutine/channel) formalism which is easy to reason with.

Python is a mess with the gil and async libraries that are hard to reason with. C,C++,Java etc need external libraries to implement threading which cant be reasoned with in the context of the language itself.

So, go is a perfect fit for the http server (or service) usecase and in my experience there is no parallel.

Jtsummers

> Java etc need external libraries to implement threading

Java does not need external libraries to implement threading, it's baked into the language and its standard libraries.

ashton314

> So, go is a perfect fit for the http server (or service) usecase and in my experience there is no parallel.

Elixir handling 2 million websocket connections on a single machine back in 2015 would like to have a word.[1] This is largely thanks to the Erlang runtime it sits atop.

Having written some tricky Go (I implemented Raft for a class) and a lot of Elixir (professional development), it is my experience that Go's concurrency model works for a few cases but largely sucks in others and is way easier to write footguns in Go than it ought to be.

[1]: https://phoenixframework.org/blog/the-road-to-2-million-webs...

Fire-Dragon-DoL

I worked in both Elixir and Go. I still think Elixir is best for concurrency.

I recently realized that there is no easy way to "bubble up a goroutine error", and I wrote some code to make sure that was possible, and that's when I realize, as usual, that I'm rewriting part of the OTP library.

The whole supervisor mechanism is so valuable for concurrency.

dehrmann

> Java etc need external libraries to implement threading which cant be reasoned with in the context of the language itself.

What do you mean by this for Java? The library is the runtime that ships with Java, and while they're OS threads under the hood, the abstraction isn't all that leaky, and it doesn't feel like they're actually outside the JVM.

Working with them can be a bit clunky, though.

gf000

Also, Java is one of the only languages with actually decent concurrent data structures right out of the box.

dboreham

I think parent means they're (mostly) not supported via keywords. But you can use Kotlin and get that.

stouset

With all due respect, there are many languages in popular use that can do this, in many cases better than golang.

I believe it’s the only system you know. But it’s far from the only one.

antonchekhov

> there are many languages in popular use that can do this, in many cases better than golang

I'd love to see a list of these, with any references you can provide.

ibic

Please elaborate or give some examples to back your claim?

dismalaf

There's not that many. C/C++ and Rust all map to OS threads and don't have CSP type concurrency built in.

In Go's category, there's Java, Haskell, OCaml, Julia, Nim, Crystal, Pony...

Dynamic languages are more likely to have green threads but aren't Go replacements.

hombre_fatal

> using the CSP-like (goroutine/channel) formalism which is easy to reason with

I thought it was a seldom mentioned fact in Go that CSP systems are impossible to reason about outside of toy projects so everyone uses mutexes and such for systemic coordination.

I'm not sure I've even seen channels in a production application used for anything more than stopping a goroutine, collecting workgroup results, or something equally localized.

kbolino

There's also atomic operations (sync/atomic) and higher-level abstractions built on atomics and/or mutexes (sempahores, sync.Once, sync.WaitGroup/errgroup.Group, etc.). I've used these and seen them used by others.

But yeah, the CSP model is mostly dead. I think the language authors' insistence that goroutines should not be addressable or even preemptible from user code makes this inevitable.

Practical Go concurrency owes more to its green threads and colorless functions than its channels.

hintymad

Unless we consider JDK as external library. Speaking of library, Java's concurrency containers are truly powerful yet can be safely used by so many engineers. I don't think Go's ecosystem is even close.

gf000

Go is such a good fit for multi-core, especially that it is not even memory safe under data races..

kbolino

It is rare to encounter this in practice, and it does get picked up by the race detector (which you have to consciously enable). But the language designers chose not to address it, so I think it's a valid criticism. [1]

Once you know about it, though, it's easy to avoid. I do think, especially given that the CSP features of Go are downplayed nowadays, this should be addressed more prominently in the docs, with the more realistic solutions presented (atomics, mutexes).

It could also potentially be addressed using 128-bit atomics, at least for strings and interfaces (whereas slices are too big, taking up 3 words). The idea of adding general 128-bit atomic support is on their radar [2] and there already exists a package for it [3], but I don't think strings or interfaces meet the alignment requirements.

[1]: https://research.swtch.com/gorace

[2]: https://github.com/golang/go/issues/61236

[3]: https://pkg.go.dev/github.com/CAFxX/atomic128

asa400

Erlang.

kace91

My feeling is that in terms of developer ergonomics, it nailed the “very opinionated, very standard, one way of doing things” part. It is a joy to work on a large microservices architecture and not have a different style on each repo, or avoiding formatting discussions because it is included.

The issue is that it was a bit outdated in the choice of _which_ things to choose as the one Go way. People expect a map/filter method rather than a loop with off by one risks, a type system with the smartness of typescript (if less featured and more heavily enforced), error handling is annoying, and so on.

I get that it’s tough to implement some of those features without opening the way to a lot of “creativity” in the bad sense. But I feel like go is sometimes a hard sell for this reason, for young devs whose mother language is JavaScript and not C.

dkarl

> The issue is that it was a bit outdated in the choice of _which_ things to choose as the one Go way

I agree with this. I feel like Go was a very smart choice to create a new language to be easy and practical and have great tooling, and not to be experimental or super ambitious in any particular direction, only trusting established programming patterns. It's just weird that they missed some things that had been pretty well hashed out by 2009.

Map/filter/etc. are a perfect example. I remember around 2000 the average programmer thought map and filter were pointlessly weird and exotic. Why not use a for loop like a normal human? Ten years later the average programmer was like, for loops are hard to read and are perfect hiding places for bugs, I can't believe we used to use them even for simple things like map, filter, and foreach.

By 2010, even Java had decided that it needed to add its "stream API" and lambda functions, because no matter how awful they looked when bolted onto Java, it was still an improvement in clarity and simplicity.

Somehow Go missed this step forward the industry had taken and decided to double down on "for." Go's different flavors of for are a significant improvement over the C/C++/Java for loop, but I think it would have been more in line with the conservative, pragmatic philosophy of Go to adopt the proven solution that the industry was converging on.

j1elo

> People expect a map/filter method

Do they? After too many functional battles I started practicing what I'm jokingly calling "Debugging-Driven Development" and just like TDD keeps the design decisions in mind to allow for testability from the get-go, this makes me write code that will be trivially easy to debug (specially printf-guided debugging and step-by-step execution debugging)

Like, adding a printf in the middle of a for loop, without even needing to understand the logic of the loop. Just make a new line and write a printf. I grew tired of all those tight chains of code that iterate beautifully but later when in a hurry at 3am on a Sunday are hell to decompose and debug.

kace91

I'm not a hard defender of functional programming in general, mind you.

It's just that a ridiculous amount of steps in real world problems can be summarised as 'reshape this data', 'give me a subset of this set', or 'aggregate this data by this field'.

Loops are, IMO, very bad at expressing those common concepts briefly and clearly. They take a lot of screen space, usually accesory variables, and it isn't immediately clear from just seing a for block what you're about to do - "I'm about to iterate" isn't useful information to me as a reader, are you transforming data, selecting it, aggregating it?.

The consequence is that you usually end up with tons of lines like

userIds = getIdsfromUsers(users);

where the function is just burying a loop. Compare to:

userIds = users.pluck('id')

and you save the buried utility function somewhere else.

tuetuopay

Rust has `.inspect()` for iterators, which achieves your printf debugging needs. Granted, it's a bit harder for an actual debugger, but support's quite good for now.

const_cast

Just use a real debugger. You can step into closures and stuff.

I assume, anyway. Maybe the Go debugger is kind of shitty, I don't know. But in PHP with xdebug you just use all the fancy array_* methods and then step through your closures or callables with the debugger.

williamdclt

I'll agree that explicit loops are easier to debug, but that comes at the cost of being harder to write _and_ read (need to keep state in my head) _and_ being more bug-prone (because mutability).

I think it's a bad trade-off, most languages out there are moving away from it

lenkite

This depends on the language and IDE. Intellij Java debugger is excellent at stream debugging.

traceroute66

> Just all-around a trusty tool in the belt

I agree.

The Go std-lib is fantastic.

Also no dependency-hell with Go, unlike with Python. Just ship an oven-ready binary.

And what's the alternative ?

Java ? Licensing sagas requiring the use of divergent forks. Plus Go is easier to work with, perhaps especially for server-side deployments.

Zig ? Rust ? Complex learning curve. And having to choose e.g. Rust crates re-introduces dependency hell and the potential for supply-chain attacks.

gf000

> Java ? Licensing sagas requiring the use of divergent forks. Plus Go is easier to work with, perhaps especially for server-side deployments

Yeah, these are sagas only, because there is basically one, single, completely free implementation anyone uses on the server-side and it's OpenJDK, which was made 100% open-source and the reference implementation by Oracle. Basically all of Corretto, AdoptOpenJDK, etc are just builds of the exact same repository.

People bringing this whole license topic up can't be taken seriously, it's like saying that Linux is proprietary because you can pay for support at Red Hat..

traceroute66

> People bringing this whole license topic up can't be taken seriously

So you mean all those universities and other places that have been forced to spend $$$ on licenses under the new regime also can't be taken seriously ? Are you saying none of them took advice and had nobody on staff to tell them OpenJDK exists ?

Regarding your Linux comment, some of us are old enough to remember the SCO saga.

Sadly Oracle have deeper pockets to pay more lawyers than SCO ever did ....

pjmlp

There are other JVMs that do not descend from OpenJDK, but in general your point stands.

Luker88

> Rust crates re-introduces [...] potential for supply-chain attacks.

I have absolutely no idea how go would solve this problem, and in fact I don't think it does at all.

> The Go std-lib is fantastic.

I have seen worse, but I would still not call it decent considering this is a fairly new language that could have done a lot more.

I am going to ignore the incredible amount of asinine and downright wrong stuff in many of the most popular libraries (even the basic ones maintained by google) since you are talking only about the stdlib.

On the top of my head I found inconsistent tagging management for structs (json defaults, omitzero vs omitempty), not even errors on tag typos, the reader/writer pattern that forces you to to write custom connectors between the two, bzip2 has a reader and no writer, the context linked list for K/V. Just look at the consistency of the interfaces in the "encoding" pkg and cry, the package `hash` should actually be `checksum`. Why does `strconv.Atoi`/ItoA still exist? Time.Add() vs Time.Sub()...

It chock full of inconsistencies. It forces me to look at the documentation every single time I don't use something for more than a couple of days. No, the autocomplete with the 2-line documentation does not include the potential pitfalls that are explained at the top of the package only.

And please don't get me started on the wrappers I had to write around stuff in the net library to make it a bit more consistent or just less plain wrong. net/url.Parse!!! I said don't make my start on this package! nil vs NoBody! ARGH!

None of this is stuff at the language level (of which there is plenty to say).

None of it is a dealbreaker per se, but it adds attrition and becomes death by a billion cuts.

I don't even trust any parser written in go anymore, I always try to come up with corner cases to check how it reacts, and I am often surprised by most of them.

Sure, there are worse languages and libraries. Still not something I would pick up in 2025 for a new project.

brabel

You forgot D. In a world where D exists, it's hard to understand why Go needed to be created. Every critique in this post is not an issue in D. If the effort Google put into Go had gone on making D better, I think D today would be the best language you could use. But as it is, D has had very little investment (by that I mean actual developer time spent on making it better, cleaning it up, writing tools) and it shows.

maleldil

I don't think the languages are comparable. Go tries to stay simple (whatever that means), while D is a kitchen-sink language.

tempay

> Rust crates re-introduces dependency hell and the potential for supply-chain attacks.

I’m only a casual user of both but how are rust crates meaningfully different from go’s dependency management?

jaas

Go has a big, high quality standard library with most of what one might need. Means you have to bring in and manage (and trust) far fewer third party dependencies, and you can work faster because you’re not spending a bunch of time figuring out what the crate of the week is for basic functionality.

tzekid

I think it's because go's community sticks close to the standard library:

e.g. iirc. Rust has multiple ways of handling Strings while Go has (to a big extent) only one (thanks to the GC)

morsecodist

This just makes it even more frustrating to me. Everything good about go is more about the tooling and ecosystem but the language itself is not very good. I wish this effort had been put into a better language.

iTokio

Go has transparent async io and a very nice M:N threading model that makes writing http servers using epoll very simple and efficient.

The ergonomics for this use case are better than in any language I ever used.

theshrike79

uv + the new way of adding the required packages in the comments is pretty good.

you can go `uv run script.py` and it'll automatically fetch the libraries and run the script in a virtual environment.

Still no match for Go though, shipping a single cross-compiled binary is a joy. And with a bit of trickery you can even bundle in your whole static website in it :) Works great when you're building business logic with a simple UI on top.

richid

I've been out of the Python game for a while but I'm not surprised there is yet another tool on the market to handle this.

You really come to appreciate when these batteries are included with the language itself. That Go binary will _always_ run but that Python project won't build in a few years.

lenkite

uv is the new hotness now. Let us check back in 5 years...

traceroute66

> you can go `uv run script.py` and it'll automatically fetch the libraries and run the script in a virtual environment.

Yeah, but you still have to install `uv` as a pre-requisite.

And you still end up with a virtual environment full of dependency hell.

And then of course we all remember that whole messy era when Python 2 transitioned to Python 3, and then deferred it, and deferred it again....

You make a fair point, of course it is technically possible to make it (slightly) "cleaner". But I'll still take the Go binary thanks. ;-)

null

[deleted]

porridgeraisin

> std-lib

Yes, My favourite is the `time` package. It's just so elegant how it's just a number under there, the nominal type system truly shines. And using it is a treat. What do you mean I can do `+= 8*time.Hour` :D

tux3

Unfortunately it doesn't have error handling, so when you do += 8 hours and it fails, it won't return a Go error, it won't throw a Go exception, it just silently does the wrong thing (clamp the duration) and hope you don't notice...

It's simplistic and that's nice for small tools or scripts, but at scale it becomes really brittle since none of the edge cases are handled

pansa2

As long as you don’t need to do `hours := 8` and `+= hours * time.Hour`. Incredibly the only way to get that multiplication to work is to cast `hours` to a `time.Duration`.

In Go, `int * Duration = error`, but `Duration * Duration = Duration`!

candiddevmike

The way Go parses time strings by default is insane though, even the maintainers regret it. It's a textbook example of being too clever.

giantg2

"Concurrency is tricky"

This tends to be true for most languages, even the ones with easier concurrency support. Using it correctly is the tricky part.

I have no real problem with the portability. The area I see Go shining in is stuff like AWS Lambda where you want fast execution and aren't distributing the code to user systems.

theshrike79

People tend to refer to the bit where Discord rewrote a bit of their stack in Rust because Go GC pauses were causing issues.

The code was on the hot path of their central routing server handling Billions (with a B) messages in a second or something crazy like that.

You're not building Discord, the GC will most likely never be even a blip in your metrics. The GC is just fine.

AtlasBarfed

I get you can specifically write code that does not malloc, but I'm curious at scale if there are heap management / fragmentation and compression issues that are equivalent to GC pause issues.

I don't have a lot of experience with the malloc languages at scale, but I do know that heat fragmentation and GC fragmentation are very similar problems.

There are techniques in GC languages to avoid GC like arena allocation and stuff like that, generally considered non-idiomatic.

SkepticalWhale

Go has its fair share of flaws but I still think it hits a sweet spot that no other server side language provides.

It’s faster than Node or Python, with a better type system than either. It’s got a much easier learning curve than Rust. It has a good stdlib and tooling. Simple syntax with usually only one way to do things. Error handling has its problems but I still prefer it over Node, where a catch clause might receive just about anything as an “error”.

Am I missing a language that does this too or more? I’m not a Go fanatic at all, mostly written Node for backends in my career, but I’ve been exploring Go lately.

ecshafer

> It’s faster than Node or Python, with a better type system than either. It’s got a much easier learning curve than Rust. It has a good stdlib and tooling. Simple syntax with usually only one way to do things. Error handling has its problems but I still prefer it over Node, where a catch clause might receive just about anything as an “error”.

I feel like I could write this same paragraph about Java or C#.

acedTrex

Java and C# are both languages with A LOT more features and things to learn. Go someone can pick 80% of the language up in a single day.

bob1029

Just because you can learn about something doesn't mean you need to. C# now offers top-level programs that are indistinguishable from python scripts at a quick glance. No namespaces, classes or main methods are required. Just the code you want to execute and one simple file.

https://learn.microsoft.com/en-us/dotnet/csharp/fundamentals...

gf000

Java is a very tiny language. I don't buy that it would take significantly longer to learn.

gadflyinyoureye

And they will trip over the remains 20% percent for the rest of their days.

SkepticalWhale

I mostly agree with you except the simple syntax with one way of doing things. If my memory serves me, Java supports at least 2 different paradigms for concurrency, for example, maybe more. I don’t know about C#. Correct me if wrong.

mattmanser

But that's only because they're older and were around before modern concurrent programming was invented.

In C#, for example, there are multiple ways, but you should generally be using the modern approach of async/Task, which is trivial to learn and used exclusively in examples for years.

bccdee

Yeah the big problem is that most languages have their fair share of rough edges. Go is performant and portable* with a good runtime and a good ecosystem. But it also has nil pointers, zero values, no destructors, and no macros. (And before anyone says macros are bad, codegen is worse, and Go has to use a lot of codegen to get around the lack of macros).

There are languages with fewer warts, but they're usually more complicated (e.g. Rust), because most of Go's problems are caused by its creators' fixation with simplicity at all costs.

genshii

Maybe this is a bit pedantic, but it bothers me when people refer to "Node" as a programming language. It's not a language, it's a JavaScript runtime. Which to that you might say "well when people say Node they just mean JavaScript". But that's also probably not accurate, because a good chunk of modern Node-executed projects are written in TypeScript, not JavaScript. So saying "Node" doesn't actually say which programming language you mean. (Also, there are so many non-Node ways to execute JavaScript/TypeScript nowadays)

Anyway, assuming you're talking about TypeScript, I'm surprised to hear that you prefer Go's type system to TypeScript's. There are definitely cases where you can get carried away with TypeScript types, but due to that expressiveness I find it much more productive than Go's type system (and I'd make the same argument for Rust vs. Go).

SkepticalWhale

My intent was just to emphasize that I’m comparing Go against writing JavaScript for the Node runtime and not in the browser, that is all, but you are correct.

Regarding Typescript, I actually am a big fan of it, and I almost never write vanilla JS anymore. I feel my team uses it well and work out the kinks with code review. My primary complaint, though, is that I cannot trust any other team to do the same, and TS supports escape hatches to bypass or lie about typing.

I work on a project with a codebase shared by several other teams. Just this week I have been frustrated numerous times by explicit type assertions of variables to something they are not (`foo as Bar`). In those cases it’s worse than vanilla JS because it misleads.

andrewmcwatters

Yeah, but no one is using v8 directly, even though technically you could if you wanted. Node.js is as much JavaScript as LuaJIT is Lua, or GCC compiles C.

genshii

Fair, but I think the JavaScript ecosystem is unique in that the language is very separate from the thing that executes/compiles it. When you write Go, 99.9% of the time you're writing for the Go compiler.

When you write JavaScript (or TypeScript that gets transpiled), it's not as easy to assume the target is Node (V8). It could be Bun (JavaScriptCore), Deno, a browser, etc.

VeejayRampay

it is pedantic, everyone knows what "node" means in this context

genshii

Apparently not, because I first assumed that he was talking about TypeScript considering that JavaScript doesn't have much of type system to compare to.

viccis

>with a better type system than either

Given Python's substantial improvements recently, I would put it far ahead of the structural typing done in Go, personally.

diarrhea

Yes, Python is massively ahead there. The largest wart is that types can be out of sync with actual implementation, with things blowing up at runtime -- but so can Go with `any` and reflection.

Python, for a number of years at this point, has had structural (!) pattern matching with unpacking, type-checking baked in, with exhaustiveness checking (depending on the type checker you use). And all that works at "type-check time".

It can also facilitate type-state programming through class methods.

Libraries like Pydantic are fantastic in their combination of ergonomics and type safety.

The prime missing piece is sum types, which need language-level support to work well.

Go is simplistic in comparison.

mervz

I find Go's type system to be extremely lackluster.

SkepticalWhale

Ok can you elaborate?

ghthor

The real cream is that there barely any maintenance. The code I wrote 15years ago still works

That’s the selling point for me. If I’m coming to a legacy code as that no one working wrote, I pray it is go because then it just keeps working through upgrading the compiler and generally the libraries used.

rsyring

Maybe Nim. But it's not really caught on and the ecosystem is therefore relatively immature.

andrewmcwatters

It definitely hits a sweet spot. There is basically no other faster, widely used programming language in production used predominantly for web services than Go. You can argue Rust, but I just don't see it in job listings. And virtually no one is writing web services in C or C++ directly.

openasocket

I've worked almost exclusively on a large Golang project for over 5 years now and this definitely resonates with me. One component of that project is required to use as little memory as possible, and so much of my life has been spent hitting rough edges with Go on that front. We've hit so many issues where the garbage collector just doesn't clean things up quickly enough, or we get issues with heap fragmentation (because Go, in its infinite wisdom, decided not to have a compacting garbage collector) that we've had to try and avoid allocations entirely. Oh, and when we do have those issues, it's extremely difficult to debug. You can take heap profiles, but those only tell you about the live objects in the heap. They don't tell you about all of the garbage and all of the fragmentation. So diagnosing the issue becomes a matter of reading the tea leaves. For example, the heap profile says function X only allocated 1KB of memory, but it's called in a hot loop, so there's probably 20MB of garbage that this thing has generated that's invisible on the profile.

We pre-allocate a bunch of static buffers and re-use them. But that leads to a ton of ownership issues, like the append footgun mentioned in the article. We've even had to re-implement portions of the standard library because they allocate. And I get that we have a non-standard use case, and most programmers don't need to be this anal about memory usage. But we do, and it would be really nice to not feel like we're fighting the language.

nasretdinov

I've found that when you need this it's easier to move stuff offheap, although obviously that's not entirely trivial in a GC language, and it certainly creates a lot of rough edges. If you find yourself writing what's essentially, e.g. C++ or Rust in Go, then you probably should just rewrite that part in the respective language when you can :)

theobeers

Perhaps the new "Green Tea" GC will help? It's described as "a parallel marking algorithm that, if not memory-centric, is at least memory-aware, in that it endeavors to process objects close to one another together."

https://github.com/golang/go/issues/73581

openasocket

I saw that! I’m definitely interested in trying it out to see if it helps for our use case. Of course, at this point we’ve reduced allocations so much the GC doesn’t have a ton of work to do, unless we slip up somewhere (which has happened). I’ll probably have to intentionally add some allocations in a hot path as a stress test.

What I would absolutely love is a compacting garbage collector, but my understanding is Go can’t add that without breaking backwards compatibility, and so likely will never do that.

arccy

I guess you'd be interested in the arena experiment, though it seems to be currently on pause

daxfohl

Embed a local redis or sqlite instance?

diarrhea

Using SQLite in Go is also not for the faint of heart either.

andrewmcwatters

I know this comment isn't terribly helpful, so I'm sorry, but it also sounds like Go is entirely the wrong language for this use case and you and your team were forced to use it for some corporate reason, like, the company only uses a subset of widely used programming languages in production.

I've heard the term "beaten path" used for these languages, or languages that an organization chooses to use and forbids the use of others.

torginus

I still don't understand why defer works on function scope, and not lexical scope, and nobody has been able to explain to me the reason for it.

In fact this was so surprising to me is that I only found out about it when I wrote code that processed files in a loop, and it started crashing once the list of files got too big, because defer didnt close the handles until the function returned.

When I asked some other Go programmers, they told me to wrap the loop body in an anonymus func and invoke that.

Other than that (and some other niggles), I find Go a pleasant, compact language, with an efficient syntax, that kind of doesn't really encourage people trying to be cute. I started my Go journey rewriting a fairly substantial C# project, and was surprised to learn that despite it having like 10% of the features of C#, the code ended up being smaller. It also encourages performant defaults, like not forcing GC allocation at every turn, very good and built-in support for codegen for stuff like serialization, and no insistence to 'eat the world' like C# does with stuff like ORMs that showcase you can write C# instead of SQL for RDBMS and doing GRPC by annotating C# objects. In Go, you do SQL by writing SQL, and you od GRPC by writing protobuf specs.

gwd

So sometimes you want it lexical scope, and sometimes function scope; For example, maybe you open a bunch of files in a loop and need them all open for the rest of the function.

Right now it's function scope; if you need it lexical scope, you can wrap it in a function.

Suppose it were lexical scope and you needed it function scope. Then what do you do?

gf000

Making it lexical scope would make both of these solvable, and would be clear for anyone reading it.

You can just introduce a new scope wherever you want with {} in sane languages, to control the required behavior as you wish.

lowmagnet

You can start a new scope with `{}` in go. If I have a bunch of temp vars I'll declare the final result outside the braces and then do the work inside. But lately days I'll just write a function. It's clearer and easier to test.

tgv

Currently, you can write

    if (some condition) { defer x() }
When it's lexically scoped, you'd need to add some variable. Not that that happens a lot, but a lexically scoped defer isnt needed often either.

torginus

I never wanted function-scope defer, not sure what would be the usecase, but if there was one, you could just do what the other comments suggested.

hnlmorg

Really? I find the opposite is true. If I need lexical scope then I’d just write, for example

  f.Close() // without defer 
The reason I might want function scope defer is because there might be a lot of different exit points from that function.

With lexical scope, there’s only three ways to safely jump the scope:

1. reaching the end of the procedure, in which case you don’t need a defer)

2. A ‘return’, in which case you’re also exiting the function scope

3. a ‘break’ or ‘continue’, which admittedly could see the benefit of a lexical scope defer but they’re also generally trivial to break into their own functions; and arguably should be if your code is getting complex enough that you’ve got enough branches to want a defer.

If Go had other control flows like try/catch, and so on and so forth, then there would be a stronger case for lexical defer. But it’s not really a problem for anyone aside those who are also looking for other features that Go also doesn’t support.

masklinn

> Suppose it were lexical scope and you needed it function scope. Then what do you do?

Defer a bulk thing at the function scope level, and append files to an array after opening them.

biztos

That seems like more work, and less readability, than sticking in the extra function.

Would be nice to have both options though. Why not a “defer” package?

connicpu

You do what the compiler has to do under the hood: at the top of the function create a list of open files, and have a defer statement that loops over the list closing all of the files. It's really not a complicated construct.

anonymoushn

defer { close all the files in the collection }

?

gwd

OK, what happens now if you have an error opening one of those files, return an error from inside the for loop, and forget to close the files you'd already opened?

__s

1. it avoids a level of indentation until you wrap it in a function

2. mechanic is tied to call stack / stack unwinding

3. it feels natural when you're coming from C with `goto fail`

(yes it annoys me when I want to defer in a loop & now that loop body needs to be a function)

christophilus

I’ve worked with languages that have both, and find myself wishing I could have function-level defer inside conditionals when I use the block-level languages.

grey-area

There’s probably no deep reason, does it matter much?

torginus

Yes it does, function-scope defer needs a dynamic data structure to keep track of pending defers, so its not zero cost.

It can be also a source of bugs where you hang onto something for longer than intended - considering there's no indication of something that might block in Go, you can acquire a mutex, defer the release, and be surprised when some function call ends up blocking, and your whole program hangs for a second.

nasretdinov

I think it's only a real issue when you're coming from a language that has different rules. Block-scoping (and thus not being able to e.g. conditionally remove a temp file at the end of a function) would be equally surprising for someone coming from Go.

But I do definitely agree that the dynamic nature of defer and it not being block-scoped is probably not the best

wtetzner

Having to wrap a loop body in a function that's immediately invoked seems like it would make the code harder to read. Especially for a language that prides itself on being "simple" and "straightforward".

jayd16

You can write SQL or use protbuf spec with C#. You just also have the other options.

cabirum

Lexical scope does not have a stack to put defer onto.

masklinn

All the defer sites in a lexical scope are static, you can target those sites directly or add a fixed-size stack in the frame.

kstenerud

I used go for years, and while it's able to get small things up and running quickly, bigger projects soon become death-by-a-thousand-cuts.

Debugging is a nightmare because it refuses to even compile if you have unused X (which you always will have when you're debugging and testing "What happens if I comment out this bit?").

The bureaucracy is annoying. The magic filenames are annoying. The magic field names are annoying. The secret hidden panics in the standard library are annoying. The secret behind-your-back heap copies are annoying (and SLOW). All the magic in go eventually becomes annoying, because usually it's a naively repurposed thing (where they depend on something that was designed for a different purpose under different assumptions, but naively decided to depend on its side effects for their own ever-so-slightly-incompatible machinery - like special file names, and capitalization even though not all characters have such a thing .. was it REALLY such a chore to type "pub" for things you wanted exposed?).

Now that AI has gotten good, I'm rather enjoying Rust because I can just quickly ask the AI why my types don't match or a gnarly mutable borrow is happening - rather than spending hours poring over documentation and SO questions.

chippiewill

I haven't done serious Rust development since AI got good, but I did have a brief play last December and it's shocking how good they are at Rust. It feels like the verbose syntax and having tons of explicit information everywhere just makes it breeze through problems that would trip up a human for ages.

jact

I worked briefly on extending an Go static site generator someone wrote for a client. The code was very clear and easy to read, but difficult to extend due to the many rough edges with the language. Simple changes required altering a lot of code in ways that were not immediately obvious. The ability to encapsulate and abstract is hindered in the name of “simplicity.” Abstraction is the primary way we achieve simple and easy to extend code. John Ousterhoust defined a complex program as one that is difficult to extend rather than necessarily being large or difficult to understand at scale. The average Go program seems to violate this principle a lot. Programs appear “simple” but extension proves difficult and fraught.

Go is a case of the emperor having no clothes. Telling people that they just don’t get it or that it’s a different way of doing things just doesn’t convince me. The only thing it has going for it is a simple dev experience.

morsecodist

I find the way people talk about Go super weird. If people have criticisms people almost always respond that the language is just "fine" and people kind of shame you for wanting it. People say Go is simpler but having to write a for loop to get the list of keys of a map is not simpler.

TheDong

I agree with your point, but you'll have to update your example of something go can't do

> having to write a for loop to get the list of keys of a map

We now have the stdlib "maps" package, you can do:

   keys := slices.Collect(maps.Keys(someMap))
With the wonder of generics, it's finally possible to implement that.

Now if only Go was consistent about methods vs functions, maybe then we could have "keys := someMap.Keys()" instead of it being a weird mix like `http.Request.Headers.Set("key", "value")` but `map["key"] = "value"`

Or 'close(chan x)' but 'file.Close()', etc etc.

diarrhea

> Now if only Go was consistent about methods vs functions

This also hurts discoverability. `slices`, `maps`, `iter`, `sort` are all top-level packages you simply need to know about to work efficiently with iteration. You cannot just `items.sort().map(foo)`, guided and discoverable by auto-completion.

morsecodist

Fair I stopped using Go pre-generics so I am pretty out of date. I just remember having this conversation about generics and at the time there was a large anti-generics group. Is it a lot better with generics? I was worried that a lot of the library code was already written pre-generics.

nixosbestos

Ooh! Or remember when a bunch of people acted like they had ascended to heaven for looking down on syntax-highlighting because Rob said something about it being a distraction? Or the swarms blasting me for insisting GOPATH was a nightmare that could only be born of Google's hubris (literally at the same time that `godep` was a thing and Kubernetes was spending significant efforts just fucking dealing with GOPATH.).

Happy to not be in that community, happy to not have to write (or read) Go these days.

And frankly, most of the time I see people gushing about Go, it's for features that trivially exist in most languages that aren't C, or are entirely subjective like "it's easy" (while ignoring, you know, reality).

the_duke

I personally don't like Go, and it has many shortcomings, but there is a reason it is popular regardless:

Go is a reasonably performant language that makes it pretty straightforward to write reliable, highly concurrent services that don't rely on heavy multithreading - all thanks to the goroutine model.

There really was no other reasonably popular, static, compiled language around when Google came out.

And there still barely is - the only real competitor that sits in a similar space is Java with the new virtual threads.

Languages with async/await promise something similar, but in practice are burdened with a lot of complexity (avoiding blocking in async tasks, function colouring, ...)

I'm not counting Erlang here, because it is a very different type of language...

So I'd say Go is popular despite the myriad of shortcomings, thanks to goroutines and the Google project street cred.

cogman10

Slowly but surely, the jvm has been closing the go gap. With efforts like virtual threads, zgc, lilliput, Leyden, and Valhalla, the jvm has been closing the gap.

The change from Java 8 to 25 is night and day. And the future looks bright. Java is slowly bringing in more language features that make it quite ergonomic to work with.

theshrike79

I'm still traumatised by Java from my earlier career. So many weird patterns, FactoryFactories and Spring Framework and ORMs that work 90% of the time and the 10% is pure pain.

I have no desire to go back to Java no matter how much the language has evolved.

For me C# has filled the void of Java in enterprise/gaming environments.

CharlieDigital

C# is a highly underrated language that has evolved very quickly over the last decade into a nice mix of OOP and functional.

It's fast enough, easy enough (being very similar now to TypeScript), versatile enough, well-documented (so LLMs do a great job), broad and well-maintained first party libraries, and the team has over time really focused on improving terseness of the language (pattern matching and switch expressions are really one thing I miss a lot when switching between C# and TS).

EF Core is also easily one of the best ORMs: super mature, stable, well-documented, performant, easy to use, and expressive. Having been in the Node ecosystem for the past year, there's really no comparison for building fast with less papercuts (Prisma, Drizzle, etc. all abound with papercuts).

It's too bad that it seems that many folks I've chatted with have a bad taste from .NET Framework (legacy, Windows only) and may have previously worked in C# when it was Windows only and never gave it another look.

arethuza

To be fair those "weird patterns" weren't really Java itself but the crazy culture that grew up around it when it became "enterprise".

mohaine

That isn’t Java, but spring.

That said, if on the JVM, just use Kotlin.

Orygin

Plus it seems hopeful to think you'll be only working with "New java" paradigm when most enterprise software is stuck on older versions. Just like python, in theory you can make great new green field project but 80% of the work in the industry is on older or legacy components.

skybrian

That’s great, but are you still using Maven and Gradle? I’d want to see a popular package manager that doesn’t suck before I’d consider going back.

(Similar to how Python is finally getting its act together with the uv tool.)

imiric

That may be true, but navigating 30 years of accumulated cruft, fragmented ecosystems and tooling, and ever-evolving syntax and conventions, is enough to drive anyone away. Personally, I never want to deal with classpath hell again, though this may have improved since I last touched Java ~15 years ago.

Go, with all its faults, tries very hard to shun complexity, which I've found over the years to be the most important quality a language can have. I don't want a language with many features. I want a language with the bare essentials that are robust and well designed, a certain degree of flexibility, and for it to get out of my way. Go does this better than any language I've ever used.

gf000

I can reasonably likely run a 30 years old compiled, .jar file on the latest Java version. Java is the epitome of backwards and forward-compatible changes, and the language was very carefully grown so the syntax is not too indifferent, someone hibernated since Java 7 will probably have no problem reading Java 25 code.

> Go, with all its faults, tries very hard to shun complexity

The whole field is about managing complexity. You don't shun complexity, you give tools to people to be able to manage it.

And Go goes the low end of the spectrum, of not giving enough features to manage that complexity -- it's simplistic, not simple.

I think the optimum as actually at Java - it is a very easy language with not much going on (compared to, say, Scala), but just enough expressivity that you can have efficient and comfortable to use libraries for all kind of stuff (e.g. a completely type safe SQL DSL)

EFreethought

There are still a LOT of places running old versions of Java, like JDK 8.

Java is great if you stick to a recent version and update on a regular basis. But a lot of companies hate their own developers.

clumsysmurf

Being able to create a self contained Kotlin app (JVM) that starts up quickly and uses the same amount of memory as the equivalent golang app would be amazing.

gf000

Graal native Image does that (though the compile time is quite long, but you can just run it on the JVM for development with hot reload and whatnot, and only do a native compile at release)

theshrike79

The comparative strictness and simplicity of Go also makes it a good option for LLM-assisted programming.

Every single piece of Go 1.x code scraped from the internet and baked in to the models is still perfectly valid and compiles with the latest version.

danenania

Yep, and Go’s discouragement of abstraction and indirection are also good qualities for LLM coding.

gf000

> And there still barely is - the only real competitor that sits in a similar space is Java with the new virtual threads

Which Google uses far more commonly than Go, still to this day.

nasretdinov

Well Google isn't really making a ton of new (successful) services these days, so the potential to introduce a new language is quite small unfortunately :). Plus, Go lacks one quite important thing which is ability to do an equivalent of HotSwap in the live service, which is really useful for debugging large complex applications without shutting them down.

gf000

Google is 100% writing a whole load of new services, and Go is 13 years old (even older within Google), so it surely has had ample opportunities to take.

As for hot swap, I haven't heard it being used for production, that's mostly for faster development cycles - though I could be wrong. Generally it is safer to bring up the new version, direct requests over, and shut down the old version. It's problematic to just hot swap classes, e.g. if you were to add a new field to one of your classes, how would old instances that lack it behave?

fabian2k

There are real pain points with async/await, but I find the criticism there often overblown. Most of the issues go away if you go pure async, mixing older sync code with async is much more difficult though.

My experience is mostly with C#, but async/await works very well there in my experience. You do need to know some basics there to avoid problem, but that's the case for essentially every kind of concurrency. They all have footguns.

null

[deleted]

zwnow

What modern language is a better fit for new projects in your opinion?

aloukissas

Elixir, with types

pmarreck

I love Elixir but you cannot compile it into a single binary, it is massively concurrent but single-threaded slow, and deployment is still nontrivial.

And lists are slower than arrays, even if they provide functional guarantees (everything is a tradeoff…)

That said, pretty much everything else about it is amazing though IMHO and it has unique features you won’t find almost anywhere else

out_of_protocol

My vote is for Elixir as well, but it's not a competitor for multiple important reasons. There are some languages in that niche, although too small and immature, like Crystal, Nim. Still waiting for something better.

P.S. Swift, anyone?

sarchertech

That doesn’t exist yet. Also Elixir is in no way a replacement for Go.

It can’t match it for performance. There’s no mutable array, almost everything is a linked list, and message passing is the only way to share data.

I primarily use Elixir in my day job, but I just had to write high performance tool for data migration and I used Go for that.

agos

yeah, if the requirement is "makes it pretty straightforward to write reliable, highly concurrent services that don't rely on heavy multithreading", Elixir is a perfect match.

And even without types (which are coming and are looking good), Elixir's pattern matching is a thousands times better than the horror of Go error handling

zwnow

This one i can get behind.

aeonik

Clojure

gf000

For web frontend: js

For ML/data: python

For backend/general purpose software: Java

The only silver bullet we know of is building on existing libraries. These are also non-accidentally the top 3 most popular languages according to any ranking worthy of consideration.

tedk-42

I'd swap java with go any day of the week. I never liked how much 'code-padding' is required with java `public static void main`

zwnow

Java, lol. Enterprise lang with too many abstractions and wrongly interpreted OOP. Absolutely not.

myaccountonhn

What about php/ruby for web?

keb_

Absolutely no on Java. Even if the core language has seen improvements over the years, choosing Java almost certainly means that your team will be tied to using proprietary / enterprise tools (IntelliJ) because every time you work at a Java/C# shop, local environments are tied to IDE configurations. Not to mention Spring -- now every code review will render "Large diffs are not rendered by default." in Github because a simple module in Java must be a new class at least >500 LOC long.

positron26

Count Rust. From what I can see, it's becoming very popular in the microservices landscape. Not hard to imagine why. Multithreading is a breeze. Memory use is low. Latency is great.

the_duke

Rust async makes it quite easy to shoot yourself in the foot in multiple ways.

Most users writing basic async CRUD servers won't notice, but you very much do if you write complex , highly concurrent servers.

That can be a viable tradeoff, and is for many, but it's far from being as fool-proof as Go.

tayo42

Some language with rust features minus memory and lifetime management and gos gc and stdlib would be possibly the language I've been waiting for.

Insanity

I wrote a book on Go, so I'm biased. But when I started using Go more than a decado ago, it really felt like a breath of fresh air. It made coding _fun_ again, less boilerplate heavy than Java, simple enough to pick up, and performance was generally good.

There's no single 'best language', and it depends on what your use-cases are. But I'd say that for many typical backend tasks, Go is a choice you won't really regret, even if you have some gripes with the language.

munificent

Often, when I have some home DIY or woodworking problem, I reach for my trusty Dremel:

* The Dremel is approachable: I don't have to worry about cutting off my hand with the jigsaw or set up a jig with the circular saw. I don't have to haul my workpiece out to the garage.

* The Dremel is simple: One slider for speed. Apply spinny bit to workpiece.

* The Dremel is fun: It fits comfortably in my hand. It's not super loud. I don't worry about hurting myself with it. It very satisfyingly shaves bits of stuff off things.

In so many respects, the Dremel is a great tool. But 90% of the time when I use it, it ends up taking my five times as long (but an enjoyable 5x!) and the end result is a wobbly scratchy mess. I curse myself for not spending the upfront willpower to use the right tool for the job.

I find myself doing this with all sorts of real and software tools: Over-optimizing for fun and ease-of-entry and forgetting the value of the end result and using the proper tool for the job.

I think of this as the "Dremel effect" and I try to be mindful of it when selecting tools.

Insanity

That's a fun analogy.

Most of my coding these days is definitely in the 'for fun' bucket given my current role. So I'd rather take 5x and have fun.

That said, I don't think Go is only fun, I think it's also a viable option for many backend projects where you'd traditionally have reached for Java / C#. And IMO, it sure beats the recent tendency of having JS/Python powering backend microservices.

ttz

every language has its problems; Go I think is pretty good despite them. not saying points raised in the article are invalid, you def have to be careful, and I hate the "nil interface is not necessarily nil" issue as much as anyone.

It's hard to find a language that will satisfy everyone's needs. Go I find better for smaller, focused applications/utilities... can definitely see how it would cause problems at an "enterprise" level codebase.

fschuett

Technically, the term "billion dollar mistake", coined in 1965, would now be a "10 billion dollar mistake" in 2025. Or, if the cost is measured in terms of housing, it would be a "21 billion dollar mistake".

:^/

masklinn

The billion dollar mistake was made in 1965 but the term was coined in 2009, defined as the following:

> I couldn't resist the temptation to put in a null reference, simply because it was so easy to implement. This has led to innumerable errors, vulnerabilities, and system crashes, which have probably caused a billion dollars of pain and damage in the last forty years.

ergonaught

For the most part I've loved Go since just before 1.0 through today. Nits can surely be picked, but "it's still not good" is a strange take.

I think there is little to no chance it can hold on to its central vision as the creators "age out" of the project, which will make the language worse (and render the tradeoffs pointless).

I think allowing it to become pigeon holed as "a language for writing servers" has cost and will continue to cost important mindshare that instead jumps to Rust or remains in Python or etc.

Maybe it's just fun, like harping on about how bad Visual Basic was, which was true but irrelevant, as the people who needed to do the things it did well got on with doing so.

andy_ppp

They are forcing people to write Typescript code like it’s Golang where I am right now (amongst other extremely stupid decisions - only unit test service boundaries, do not pull out logic into pure functions, do not write UI tests, etc.). I really must remember to ask organisations to show me their code before joining them.

(I realise this isn’t who is hiring, but email in bio)

candiddevmike

I do this and think it works really well...

myfunc(arg: string): Value | Err

I really try not to throw anymore with typescript, I do error checking like in Go. When used with a Go backend, it makes context switching really easy...

andy_ppp

They still throw and just have millions of try catch blocks repeated everywhere around almost every function :-/

theshrike79

Have you seen Java people write Python? Same vibe :)

keb_

Reminded me of this classic talk https://www.youtube.com/watch?v=o9pEzgHorH0

sebstefan

Ah yes. I love working at places that hire experts just to tell them how they should do the work they're an expert at.