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 generalizing.

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: a standard library curated by one vendor. Choosing a third party package is a whole dimension of the search space, and in .NET most of it never opens. In the extraction port, an agent needed a ZIP reader, an XML reader, a JSON serializer 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 license check, or a dependency added to a csproj. A question that is never asked consumes no context and produces no wrong answer.

Reflection helps for the same reason. When an agent does need an unfamiliar package, it reads the XML docs, and failing that it writes a throwaway console app that reflects over the types and prints a report. It gets back a complete, accurate answer instead of 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 generalizes 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 good 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 optimizing, 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.

Read next

Articles on context graphs, enterprise search and industrial AI

Connected knowledge for AI systems