Types don't make the model smarter. They make the loop cheaper

Rafael

Curiosity is written in C#. Almost entirely. The backend is C#. The front end is C# compiled to JavaScript through our own compiler. The document extraction engine, the OCR runtime, the guard-model runtime, the static site generator that builds this blog: all C#. When we hand work to coding agents, which is now most of the mechanical work we do, they write C# too.

So we have a stake in the argument about which languages suit LLMs, and we went looking for evidence that our choice was the right one. What we found is more interesting than a confirmation, and worth writing down, because the version of this argument that circulates is mostly wrong in both directions.

The widely repeated version is that dynamic languages are more token-efficient, and therefore cheaper and better with LLMs, because you are not paying for type annotations. It was repeated enough that search engines started answering the question that way. One widely cited post measured a 2.6x gap between C (least efficient) and Clojure (most), then found J better still at around 70 tokens per solution.

The problem is visible in that last number. A task an LLM solves in 70 tokens is not a task. Those benchmarks used Rosetta Code problems, where most of the work is printing an answer, and results on trivial problems have a long history of not generalising.

What happens when the tasks get real

Dan Luu re-ran this properly, with two substantial tasks: implementing a complete Zstd decoder from the RFC (no tests given to the agent, no internet), and a Pandoc implementation scored against a holdout test set.

At medium effort on the Zstd task, the popular claim looks true:

Zstd cost against correctness at medium effort

The dynamic languages do cluster cheap and correct. If you stopped here (and the earlier benchmarks effectively did), you would conclude that types cost you money.

Then you turn the effort up, and run a second, very different task:

Pandoc holdout score against Zstd ultra score

The clusters dissolve. F# and C++ lead on Zstd at ultra effort, with JavaScript and Python right behind them; Rust and Swift sit below Ruby and PHP. On Pandoc, Python leads, Rust and PHP come next, and C# sits a little behind them. The separation that does show up is not about types at all: obscure languages (J, Factor) and assembly do badly, and Dan Luu's own read is that there is weak support for "use a popular language" and essentially none for the static/dynamic distinction in either direction.

Worth stating plainly, because it cuts against the post we would like to have written: this data does not show that statically typed languages produce better LLM code. The author pre-registered a guess that static languages would edge ahead at ultra effort and, on his own results, called that guess wrong.

A second benchmark, and the thing both agree on

The gertlabs rankings score languages across a different suite, and there the typed languages do come out ahead:

Language comparison by average percentile

Kotlin, OCaml and C# take the top three, with Go and Python last.

The two benchmarks disagree about whether type discipline is the variable doing the work. Go is statically typed and comes last here; Dan Luu's tasks find no split at all. But they agree on something more directly useful to us: C# does well in both. Third of eleven in the rankings above, and on Dan Luu's two tasks it sits in the upper third of twenty-five: near the top on Pandoc, and comfortably inside the leading cluster on Zstd at ultra effort.

That is a better summary than either "types win" or "types don't matter". Whichever mechanism turns out to explain these numbers, the language we already use is not costing us anything on them, and on one of the three it is at the front.

So why are we still all-in on C#?

Because the thing that costs money in agentic development is iterations that fail late, not tokens per line of source.

An agent writing code runs a loop: propose a change, get feedback, correct. The cost of that loop is set by how quickly and how precisely the feedback arrives. A strongly typed target collapses a large class of errors into the fastest, most precise feedback available: a compiler error with a file, a line and a reason, before any test runs.

This is worth being specific about, because "types catch bugs" is the vague version. When we ported a large Rust document-extraction engine to C#, the mistakes the compiler caught for free were: fields that did not exist on the target type, an enum arm dropped during translation, a nullable reference where the Rust had a non-optional value, and, repeatedly, two int parameters swapped at a call site. In a dynamic language every one of those is a runtime failure discovered by a test, if a test happens to cover it, minutes later and several turns downstream. The model then has to reason backwards from a failing assertion instead of being handed the line.

The second reason has nothing to do with types, and we think it is underrated. bob1029 put it well on Hacker News:

I think a lot of people are sleeping on the advantages of "batteries included" ecosystems. The need to select an appropriate 3rd party library represents an entire dimension of the search space that can be eliminated. Imagine having to make this choice multiple times per day when your competition is just mindlessly using System.* types. The fact that the .NET ecosystem is curated by one entity should not be underestimated.

That matches our experience exactly. In the extraction port, an agent needed a ZIP reader, an XML reader, a JSON serialiser with custom converters for tagged unions, and SIMD tensor primitives. Every one of those is in the base class library. Not one of them was a decision, a comparison of three packages, a licence check, or a dependency added to a csproj. A question that is never asked consumes no context and produces no wrong answer.

The same comment makes a point about reflection that we would not have thought to make:

Even when we do need to import 3rd party nugets, the models seem to follow this highly structured pattern. They scan the xml docs, and failing that they will build a throwaway console app to reflect over all the unique types and build a report.

An agent facing an unfamiliar .NET API can enumerate it mechanically and get back a complete, accurate answer. That is a much better position than guessing from training data and finding out at runtime.

What our own experience is worth as evidence

Not much on its own.

We ported roughly 142,000 lines of C# in that extraction engine, across 85 commits, every one of them written by an agent and reviewed by a human. It did not take months and it did not cost thousands of dollars in tokens.

But that is a single data point with at least two large confounders. We built a golden-file oracle for that project (a generator that produced reference output from the original Rust for 2,498 fixtures, and a runner that diffed against it), and we think the oracle mattered more than the language did. Porting is also the friendliest possible shape of work for an agent: the specification is executable and sitting right there. Neither of those generalises to writing a new product feature.

What we can say is narrower and, we think, more useful than the usual claim: the languages where agents work well for us are the ones where being wrong is cheap to discover. A fast compiler with precise errors is one way to get that. A comprehensive test suite is another, and a dynamic language with good tests will beat a typed language with bad ones. Read together, the benchmarks above suggest type discipline on its own is not the variable worth optimising, while also putting C# near the front of every one of them, which is a pleasant place to be standing when the mechanism is still unsettled.

If you want to make your codebase easier for agents to work in, the highest-leverage changes we have found are not linguistic. Shorten the feedback loop. Make failures precise and local. Give the agent something machine-checkable to aim at. We happen to get a lot of that from C#, which is a reason to keep using it — not a reason for anyone to switch.


Sources: How does programming language affect token efficiency and correctness? by Dan Luu, whose interactive charts are worth reading in full. Our versions were rebuilt from the values plotted in the published figures, so treat them as close rather than exact. Language rankings from gertlabs.com/rankings, reproduced as published. The Hacker News comment is from bob1029, in this thread.

Read next

Articles on context graphs, enterprise search and industrial AI

Connected knowledge for AI systems