back
170 comments
Author here, wasn't expecting this piece of writing to show up on HN.

The specifics of Python were chosen only due to the language ecosystem being fragmented and inconsistent while Python remains an essential learning, research, and now ML programming language (it was my first language and I still love it).

My thoughts on LLM generated code have changed immensely in the last 9 months as I've taken on teams and projects through my consulting work [1] as a fractional CTO. Python remains a difficult, flakey, and inconsistent programming language for complex production systems. Most other programming languages suffer from fragmented toolchains and ecosystems: JavaScript (famously), PHP, and even C/C++ to a degree.

Languages with a single way to do things benefit the most: Ruby, Rust, Swift (even). Low entropy is the way to go and convention > configuration seems to pay off with LLMs.

Mean cost of management is more important than specific edge examples "X company run on Y language". I think that 'boring' languages with rock-solid compilers, toolchains, testing frameworks, and package managers make for high return on engineering time and production maintenance.

[1]: sancho.studio

> Languages with a single way to do things benefit the most: Rust

I posit that Rust is the optimal language to emit from LLMs unless you have to target web, a specific platform, or a legacy project:

- The required error handling for Option<T>, Result<T,E>, and required destructuring of sum types naturally reduces errors by an order of magnitude

- If it compiles, chances are higher the code is correct. Especially if you're using strong typing.

- The training data for Rust is likely of a higher quality than, say, Javascript

- The resulting code is fast and portable

- You get really nice threading and async, and you don't have to think about the silly "color problem" because the LLM handles it for you.

- Using an LLM takes away any trouble you'd have with the borrow checker or refactoring, or otherwise working in a slightly more difficult language.

- Applications are single binary executables.

Since LLMs let you generate and manipulate Rust code as fast as you would Python, why not just emit Rust instead? It's the least brittle language, and it's incredibly performant.

> Languages with a single way to do things benefit the most: Ruby

I love ruby - but surely it's closer to Perl and "There's more than one way to do it" - than python which generally strives for "There should be one-- and preferably only one --obvious way to do it."?

https://legacy.python.org/dev/peps/pep-0020/

Swift is something that works very well if you enforce Swift 6 + good architecture and doesn’t work very well and ends up with slop if you don’t.

Usually once the project is already established and has good patterns, both Claude and GPT will continue the good patterns, but you may still want to add a review pass step to remove bad practices (usually hacks around concurrency instead of doing it properly).

I agree with the idea that boringly predictable should be what is preferred but anecdotally my experience in using Go with LLMs is that they trip up a lot on the races and locking from go’s thread model. I haven’t seen the same problem in rust which is now why I’m doing all my LLM work for tooling in rust.

The parallelism issue in particular was also not something I noticed agent struggling with in JavaScript, although JavaScript concurrency model is clearly fundamentally different.

The concurrency issues that I saw LMM‘s face was one reason why I created freelang which uses a very boring and audible concurrency model of OS processes that use the file system to talk instead of IPC, shared state, or anything like that. Higher overhead, lower throughput, but more boring and hopefully less bugs: https://github.com/DO-SAY-GO/freelang

Personally I generally try to avoid concurrency when writing code with AI since I feel AI makes concurrency unnecessary complex in Golang.
When you're working on something difficult that requires a model to reason intelligently, lower level and strongly typed languages often outperform on the same problems [0]. We have a few hypotheses about why, with a moderately high correlation between performance and token density of the output program -- i.e. more token dense languages are more difficult for programs to reason about.

Most models come up with the least effective solutions when writing Python.

[0] https://gertlabs.com/rankings

Your theory about token density seems reasonable, but your data doesn't seem to really match it.

Very little difference between TypeScript and JavaScript, which are essentially the same language, just one has more tokens.

Functional languages like Clojure and OCaml are pretty dense, I would have expected them to feature lower.

Kotlin is in some ways a more token dense version of Java, yet Kotlin leads, and Java is almost last.

That's a very interesting page, but the language ranking is wildly different for "average percentage" (python bottom) and "success rate" (python second). Sounds like there is some subtly about this.
I ran a little test with Go, TypeScript, Clojure, F#, Haskell, and Rust. Token count was roughly in the same ballpark, but it used the fewest for TypeScript, then Go. The rest required a bit more. Clojure always won in terms of lines of code though, generally coming in at 1/2 the size of the Go or Typescript solutions.
Even though Python code may use more characters/LoC than say Rust in text form, it's not necessarily more token dense because LLM tokenizers are good at "compressing" its English keywords

In contrast, langs with symbol-heavy syntax (ALP as extreme example) use fewer characters but don't tokenize well in practice so aren't as efficient as one would think

Rather than "boring", this seems to be reaching for something like the concept of a "pit of success", or https://haskellforall.com/2016/04/worst-practices-should-be-... . I don't think the fact that the most common pitfalls in Go are well known should be taken as a sign that it doesn't have more esoteric pitfalls as well; it's just that the common cases (like nil) are the ones that everyone sees all the time.
I disagree. "Boring" languages leave a lot of assumptions in code, which will start to compound the more changes model (and programmers) make to the code.

The more assumptions I can move to compile time the better models are at dealing with emerging complexity.

I would go the other way with LLMs and I wish for liquid types and effects in Rust to make type specifications even more strict.

P.S. effects and liquid types and type specifications in general add a lot of busywork, but models have higher level of tolerance to busywork compared to developers.

Sounds like OxCaml is pretty close to what you want. You get access to similar capabilities as Rust, but also stricter typing and an (optional) effect system. I don't know of an equivalent to Liquid Types, but it seems like the same approach that worked for Haskell would work naturally in OxCaml.
> Python is the same story but sung in a different key. Asking a simple question like “which package manager are you using?”

This is annoying but only needs to be solved once at the start, either by the LLM or the human guiding it. A single prompt of "Set up a uv project in this directory with Python 3.13" is enough that it's never an issue again for that repo.

> Goroutines are a far more tractable primitive for coding agents than threads, callbacks, async/await, or any of the colored-function regimes that dominate elsewhere.

I disagree with this. Goroutines, along with threads, callbacks, and traditional async, are all in the same category: spaghetti of unbounded background tasks. Structured concurrency [1] on the other hand is dramatically easier to reason about. Python has support for this (in Trio and asyncio.TaskGroup) as do other languages like Kotlin and Swift. Function colouring a red herring; if anything, it's useful because it highlights the scheduling/cancellation points in your code.

[1] https://vorpus.org/blog/notes-on-structured-concurrency-or-g...

-----

This really does read as "Go is my favourite language". In fairness, that's a good reason to choose a language to use with an LLM (so long as it's powerful enough and not too obscure). But let's not pretend it's the best language for everyone.

LLMs have a limited context window - similar to the limited attention span and memory of humans. LLMs also have trouble attending to many constraints at once.

Therefore the best language for agents is likely the one that, on one hand erases all irrelevant details (ie. raises the level of abstraction and does not force focusing on eg. memory management), and on the other hand encodes any domain-relevant details in the code (eg. using advanced type systems, annotations, contracts, spec-like tests eg. property-based).

Human readability is a separate concern and still relevant, but the two mentioned properties actually generally improve on that as well (at least for engineers persistent enough to scale the tower of abstraction).

Based on this, it seems Go is certainly not that "agent endgame" language. It has large amounts of boilerplate, a general lack of safety around concurrency features, a pretty middling static safety story overall with a generally underpowered type system.

I don't think the perfect language exists, yet, but just wildly imagining, it would probably be something like a cross between Scala, Elixir and Lean (or equivalents). Unfortunately none of those languages also have the large training corpus required to make them perform well in all agenting engineering situations (yet).

For any language comparison, one must separate the expressiveness of the language, which limits the long-term possibilities for agents, and the training corpus, which is what mostly gives it the current standing. I think we are still in the phase where the languages are separated by essentially random non-design factors such as the amount of training environments the frontier labs are willing to create for them.

Given that, the syntax does not matter all that much, as long as the base language itself is flexible enough - as a another wild idea, it's also possible that eg. Python could mostly swallow all these features through external tools (eg. the pre-existing type checkers or linters), and if the frontier labs bother to RL on those tools, that would also work (see also: Mojo).

This is an interesting idea, but I'd want to see something solid before acting on it.

From what I can tell, LLMs know/use patterns above the syntax and idioms of specific languages and the syntax and idioms of specific languages and how to apply the former to the latter.

The bottleneck isn't what languages the LLM can handle, but what I can handle coming out of the LLM. The general advice, then, is to use the language (and related setup/environment) you're familiar with.

I don't think the python package manager is the high level difficulty for LLMs doing python. I think the high level difficulty are nonlocal effects. At any given callsite, it might be difficult to know exactly what is going to happen to the data you pass into the call.
Contradictory anecdote: there’s basically only one way to write Elm, as it is a very trend-resistant language with minimal updates over long timespans, but most agents in my experience will throw Haskell syntax and Prelude functions into their Elm output. Compiler or LSP will often set them right but they still try it initially
Instead of empty theorizing, we should have benchmarks for this. There is at least one benchmark which suggests that LLMs are better at writing Elixir than most other languages: see the AutoCodeBenchmark.
Java 21, Spring Boot 4.x, Spring AI 2.x - probably most boring stack that is working fantastic for me to generate solid, reliable code for agents, mcp servers using Claude Code or Cursor.
I can probably fix package manager issues by hand, and quickly with a little rubber ducking with the LLM itself. I'm not sure that's a huge problem in the grand scheme.

There's a lot of stuff in Python's favor in regard to coding with LLMs: its wildly popular so there's a lot of references for the right and wrong ways to use it, it can be typed using included libraries - its as simple as telling the LLM "use typing for this", and there are several great lint and unit testing tools to cover the hallucinations and poor decisions. The flexibility seems like an advantage to me personally, but I've always been a Python stan.

I think use any language that can achieve / or is close to native speed and has a reasonable ecosystem of significant libraries around it. Trivial libs are pretty much dead as AI will implement what you need, so if you need something like MQTT, its much easier when you have mature lib that handles that. I've experimented a bunch of language with LLM, like Go, Rust, C, C++, C#, Kotlin. All work fine. My decision on what to use depends on what the larger ecosystem provides and what I'm programming for (embedded, backend, Web, GUI, App etc). I'd probably add in swift if I get around to doing iOS stuff. There's no real "best" here, multiple options are likely going to be fine choices. Crazy thing is, if you don't like your language choice you can use AI to change it (ideally early on). Just for fun I got AI to convert one of my TUI apps to various languages. Went reasonably well.
In my experience, LLMs benefit greatly from the existence of sum types and exhaustive pattern matching.
I program in two languages: Swift (my main language), for client work, and PHP, for backend work. It’s overwhelmingly Swift.

In the last year or so, I have been using LLMs, to assist my work, with generally, excellent results.

I have noticed that the LLM delivers much better PHP, than Swift. I seldom need to rewrite or correct, the PHP code I get from it, and am constantly correcting the Swift. Part of the reason, may be that I am a much better Swift programmer, than PHP programmer, and there’s just a lot more Swift code. I haven’t really taken the time to analyze it.

I have my theories, as to why, but it’s not something I’m really into researching. I’ve just noted the trend.

My experience a year ago (back when half of HN was still in denial about what was already working, let alone what was to come) was that Python was the linqua franca of LLMs. You could achieve almost anything that fit in 700 lines or less if you told it to write it in Python.

Times change, and I work more in R&D space than on legacy codebases, but I still ask it to write something in Python then convert it to the actual language on occasion. I don't know if I'm tricking the context window, forcing alternate pathways, or both, but it works.

> Languages and ecosystems with low variance in their training corpus are represented better and executed more reliably by coding agents.

So I think the author is saying that go is a simple language that tends to have less solutions to the same problem. I personally agree to that to a degree.

What I don't agree on is that we can choose what "low variance" is. There is a lot of go code out there, it's shape may have little "noise", but the variance is massive.

We are the point now where we let LLM dictate the language?
"The concurrency model is the first of these. Goroutines are a far more tractable primitive for coding agents than threads, callbacks, async/await, or any of the colored-function regimes that dominate elsewhere. They are simple, type-safe, and ubiquitously used in the corpus the model was trained on. There is no question of what color your function is, because the question does not exist."

I don't really buy the intuition (aka Goroutines are more 'clear' than 'coloured' functions or threads), and there's no evidence presented for this either.

Although this could very well be true, I'm doubtful without seeing some real world data points.

The 'general premise' aka 'cosine similarity' may have been true before bit it may not be that anymore.

AI just pretty good at anything it's 'seen enough' and that's it, I think it's more likely a 'threshold' problem than an ability problem, at least for most things.

'Rust' may represent a different domain, given the very detailed nature of notation and the vast possibilities that arise from that.

Large codebases are much easier to manage with type safety. Not a fan of Go but definitely much better than python in this regard.
I haven’t had an issue using Python with LLMs where I have to decide “Should one use pip, poetry, or uv?” Since there is enough training data using pip or just choose that since it is the most boring solution and many of the commands map to uv since uv has a superset of features. Not that go is a bad solution honestly I would just say use what you know best.
I wonder what we end up with as an LLM-friendly programming language. It's likely to be something rather formal, with entry and exit assertions. Humans hate writing those, but LLMs need them to keep them on track and give them goals.
For myself, I've generally setup a few boundaries... for JS projects, I tend to use Deno for tooling, even targeting npm lately. Similarly, I've favored modern TypeScript over JS. Often Hono + OpenAPI + Zod as a set for services.

I've also been doing quite a bit of Rust for web services and wasm targets, which has worked exceedingly well... similarly with Tokio + Axum, etc.

I have seen very few issues with either of the above... that said, C# has been a bit more painful by comparison... I often rely on FastEndpoints for services and Grate for database migrations, and LLMs often get a bit tangled with those libraries in practice.

Why are we having computer programs generate source code in the first place? Shouldn't they generate something lower level, like an AST or some computational graph or something? Source code is made to be written and read by humans, and is then translated into machine code via various transformations. In theory a program should look the same to a computer no matter which language it started out as.

We have decades of compiler research, static code analysis etc, why do these extremely complicated black boxes of billions of parameters have to produce readable source code as their main output?

I'm developing a terminal agent (https://GitHub.com/laszukdawid/trrminal-agent) in golang and can't say that it's easier or less error prone to write it in golang vs Python or JS. There's still plenty of bad ideas and bad code being suggested by Claude Code / Codex so it's still hands on work. However, testing is much easier and it makes me think more about the arch more than with Python.
I think _some_ but not _too much_ typechecking is the sweet spot for LLMs.

Without any typechecking, LLMs obviously find it harder to work agentically and validate their work.

With too much typechecking (I'm looking at you, rust), I've found agents get themselves stuck in local "architectural minima" and end up doing insane shit to mitigate ownership/borrow-checker issues inherent in the design they ended up with.

That said, if you're hands-on I think rust is a fantastic language for pairing with an LLM.

I wonder if the training data for some languages has higher quality code. I can imagine some niche languages having a higher standard than, for example Python, which surely has a bunch of random buggy scripts in the mix.

On the other hand, even if that were true, I don’t know how important it would actually be since LLMs can generalise across languages well.

It might be best to pick languages where it’s just harder to screw up, the canonical example being to prefer typescript over JavaScript.

I think that this not only applies to languages, but general patterns that you use. Don't mix functional with OO. Don't mix repositories with DAOs. Don't mix MVC and MVVM. Code should be predictable in what it does and what you expect from other developers how to code. If you don't have that then you shouldn't blame LLM when it goes haywire and starts doing whatever
Just want to throw the other Google language into the ring. While I would say Dart has a few more fancy language features than Go, it has an extremely strong and modern cli tool, which is a one stop shop for all your formatting, linking, and project building needs. It even grades how well your project is constructed before you publish it to pub.dev
> From a model’s standpoint, there are simply too many ways to write any of this

They seem quite good at figuring this out in my experience

Has Go become a "boring language"?
Anyone use this stuff with Delphi? I've been looking for tips for getting the best out agents for Delphi
Haskell is I think a great language for llms - just make everything as pure as possible and you are golden.
Though I dont know GO, but reading your post - really have me thinking that okayy this is a cool and easy language too

But as someone who is working in python since ages - I guess it is pretty much easy too, and as not as hard as you described. LOL, but whatever, your this post was really amazing

This made me remember of a benchmark that I saw a few months ago about LLMs being unexpectedly _very good_ with Perl when compared to any other language. I couldn't find it right now. If someone knows what I'm talking about, please post it here :)
Gleam is a new kid, but it seems to fit this trend: - Less ways to write code; - Strongly typed; - Erlang parallelism; - Exhaustive pattern matching; - No nulls (and many other stuff); - Pipelines (not sure about LLMs, but it fits my eye);
Great post showing the ironic revenge of opinionated architecture in times of cheap code. Exactly what LLMs can’t deliver, they always seem to be bias towards added complexity, not simplification.
I was a little surprised to find when I gave an LLM REPL access to the running program, it readily started using it during development and debugging.
+1 for boring. Boring code is Solid Code, in the sense of "Writing Solid Code" - the old book by Steve Maguire.