View as MarkdownUpdated 2026-09

Which language should you hand to an agent?

Seven languages, ranked by how much of a generated mistake survives to production. The answer is mostly about what the toolchain refuses to accept.

Model quality is roughly constant across mainstream languages. What differs enormously is how much of a wrong answer the language and its toolchain will accept before a human has to notice.

That is the question this page is about. Not "which language do models write best" — they are all competent — but "if the model is wrong, what tells me, and when."

The ranking#

Ordered by how much of a generated mistake is caught before a human reads the diff, assuming a well-configured project.

LanguageCaught by toolingResidual risk sits inBlast radius of a miss
GoVery highConcurrencyModerate
TypeScriptHighAnything cast or assertedModerate
NestJSHighAuthorisation, pipe configHigh — it is your API
PythonMediumAsync, types, runtime behaviourModerate
JavaScriptLow by default, high if configuredAsync, everythingModerate
BashMedium (shellcheck is excellent)Destructive commandsUnbounded
CHigh if sanitizers are on, low if notMemoryUnbounded

Two things fall out of that table immediately, and they are the useful conclusions.

The two that actually matter

Configuration beats language choice. JavaScript with type-aware ESLint catches more than Python with nothing. C with sanitizers catches more than either. The variance within a language, between a configured and unconfigured project, is larger than the variance between languages.

Blast radius is a separate axis from catch rate. Bash and C are not the riskiest because models write them badly — they write them fine. They are riskiest because a miss is unrecoverable. A wrong Python function returns a wrong number. A wrong rm deletes your work.

Go — the compiler does the review#

Go's design decisions, made for entirely different reasons, turn out to be close to ideal for this.

Unused variables and unused imports are compile errors, not warnings, so the debris of an abandoned approach cannot survive. There are no implicit conversions. There is one loop construct, no inheritance, no decorators, no metaclasses, and one formatter that nobody configures. The specification is small enough to read in an afternoon, which means generated Go rarely misuses an obscure feature — there are barely any.

Most importantly, errors are values. Ignoring one requires writing _, which is visible, greppable and reviewable. The single most common failure in every other language — the silently swallowed error — is structurally hard here.

shell
go build ./... && go vet ./... && go test -race ./...

Seconds, and a large fraction of what could be wrong is ruled out.

Where it still bites: concurrency. Goroutine leaks, unbuffered-channel deadlocks and unsynchronised shared state are where generated Go is genuinely weak, and the compiler stops helping exactly there. -race and goleak are not optional. Details in the Go failure modes.

TypeScript — the specification is executable#

A type is a constraint the compiler enforces on every edit, at zero marginal cost, that a model cannot talk its way around. That makes it a far more reliable control than any instruction file.

The catch is that TypeScript's defaults are deliberately permissive, and generated code lives in exactly the gaps. noUncheckedIndexedAccess is off by default, so arr[0] is typed as present when it is not. as is an assertion that checks nothing. Non-null ! is a claim nobody verifies.

Turn those on and the review becomes almost mechanical: grep the diff for as, any, ! and @ts-ignore, and you have found most of what could be wrong. Full treatment in the type system as a harness.

NestJS — structure makes review fast, and the stakes are high#

Nest is the odd one out here because the risk is not linguistic. TypeScript catches the type errors; what survives is authorisation — code that confirms a valid user and never confirms it is their resource. That is the most common real vulnerability in generated back-end code in any framework, no linter finds it, and the test that catches it is one line.

What Nest gives you in exchange is placement. A generated Express app can put a query anywhere; a generated Nest app puts it in a service, injected into a controller, registered in a module, because the framework accepts no other shape. So review is checking a small number of known locations rather than reading everything. The Nest failure modes are five greps long.

Python — the pragmatic middle#

The largest ecosystem, the most training data, and consequently very fluent generated code. The toolchain is now genuinely good: ruff with a real rule set catches roughly two thirds of the failure catalogue automatically, and a type checker catches much of the rest.

The gap is that none of it is on by default. A fresh Python project checks nothing, and the failure that hurts most — a blocking call inside async def — produces code that reads perfectly, passes every test, and only shows up as latency under concurrent load, weeks later.

Verdict: excellent to delegate, provided you spend an hour on ruff, mypy and a fast test suite first.

JavaScript — the widest configured-versus-not gap#

There is no compiler, the runtime accepts almost anything, and a wrong program usually runs until it does not. Generated JavaScript is also drawn from fifteen years of training data spanning several eras of the language, so it arrives correct and written in the dialect of 2016 — var, axios, moment, callback wrapping.

But the fix is cheap and most people skip it. A jsconfig.json with checkJs: true costs one file, requires no renaming, and unlocks type-aware ESLint — including no-floating-promises, which catches the single most common bug in generated JavaScript. The JS failure modes are mostly async, and mostly caught by that one rule.

Verdict: the biggest improvement available for the least work of any language here.

Bash — small mistakes, unbounded consequences#

Generated shell is usually correct on the happy path and dangerous on every other one, because the shell in the training data — tutorials, READMEs, Stack Overflow — omits every guard for brevity.

shellcheck is genuinely excellent and catches most of it: quoting, word splitting, subshell variable loss, wrong test operators. What it cannot tell you is whether a destructive command is pointed at the right path, and that is the failure that actually hurts. rm -rf "$BUILD_DIR/" with BUILD_DIR unset is not a syntax error.

shell
set -Eeuo pipefail    # -u alone prevents the worst outcome available in this language

Verdict: delegate freely for orchestration under fifty lines, with shellcheck and the four-line header. Read every destructive line yourself. Past a hundred lines, rewrite it in something with data structures.

C — the best tooling, almost never turned on#

Everywhere else a generated bug is a wrong answer. In C it is a heap overflow that works correctly for six months.

The saving grace is that C has the best free bug-finding tooling of any language on this list, and hardly anyone enables it. -fsanitize=address,undefined finds unchecked allocations, buffer overflows, use-after-free, leaks, signed overflow and bad shifts — with a precise stack trace at the moment of the bug. It costs about 2x runtime, which is irrelevant while developing.

Add -fanalyzer or clang-tidy for the paths you do not execute, and a fifteen-line fuzz target for anything parsing untrusted bytes, and the risk profile changes enough to make delegation reasonable. Without them it does not. The C catalogue has the specifics.

Verdict: delegate with sanitizers, or do not delegate.

If you are actually choosing#

For most of these, the language is already decided by the problem, the ecosystem and your team, and that is the right way round. But where the choice is genuinely open:

  • A networked service, greenfield, and you want the highest delegation ratio: Go. Small language, strict compiler, explicit errors, fast feedback. The verbosity people complain about is the kind that makes control flow visible — which is what you want when reading is the bottleneck.
  • Anything front-end or full-stack: TypeScript, configured strictly. The type system is the best harness available in that ecosystem.
  • Data, ML, scripting, glue: Python. The ecosystem is decisive and the tooling is now good enough.
  • A structured back-end API with several contributors: NestJS. The conventions are worth the ceremony precisely because they make generated code land in predictable places.
  • Systems work where you have the choice: memory safety by construction beats memory safety by tooling, and that argument gets stronger as generated volume rises. Where C is forced on you by the platform or the codebase, turn the sanitizers on.

The thing that actually predicts outcomes#

Across every language on this list, the same three properties separate projects where agents work well from projects where they do not — and none of them are the language:

  1. A check that runs in under five seconds and genuinely fails. Above about thirty seconds the agent stops running it and starts telling you the code should work.
  2. Feedback that is not optional. A post-edit hook that runs your linter whether or not the model asked. This is the single highest-leverage configuration change available, in every language.
  3. A reviewer who can tell in four seconds whether a diff is right. That judgement was built by writing the code, and it is the part that does not transfer.

Language choice moves the numbers. Those three move them more.

Common questions#

Is there a language models are simply better at?#

Not in a way that changes the ranking here. Python and JavaScript have the most training data and produce the most fluent output; Go produces the most reliably correct output, because there is less room to be wrong. Fluency and correctness are different axes, and the second one is what you are buying.

Should I switch languages to get a better delegation ratio?#

Almost never. The cost of a rewrite dwarfs the difference, and the configuration gap within your current language is larger than the gap between languages. Spend the afternoon on your linter and your test suite first — you will get most of the benefit and keep your ecosystem.

Where does Rust fit?#

It would sit at the top of this table on catch rate — the compiler rejects the entire class of bug that makes C dangerous, and the type system is stronger than TypeScript's. We do not have a Rust site, so it is not ranked here, but the reasoning generalises: the stricter the toolchain, the more you can safely delegate.

Does this change as models improve?#

The obvious failures are already mostly gone from frontier models. What survives is the subtle kind — a blocking call in a coroutine, a missing ownership check, a race — and those survive precisely because they produce code that reads correctly. Better models shrink the list; they do not change which languages let a mistake through silently.

Get the updates

Agent tooling moves monthly. We send a short note when something on these sites changes materially — a new harness feature, a failure mode worth knowing, a config that stopped being right. Nothing else.

Unsubscribe in one click. We never sell the list.