Why “the code compiles” and “the code is right” are two very different claims♦Photo: Slashme — CC0, via Wikimedia CommonsYou ask your AI coding assistant for a function, and what comes back looks completely reasonable. Clean formatting, sensible variable names, a comment explaining the tricky part. For a second, it feels finished.
Then you actually try to use it. The compiler rejects it over a borrow it doesn’t like. Or it compiles fine and segfaults on an input you hadn’t thought to test. Or it runs, passes every test you wrote, and turns out to be three times slower than it needed to be, because it’s using a data structure that made sense for the toy example but not for your actual dataset.
None of this means the model is bad at programming. It usually means something narrower and more interesting: the language you’re working in has exposed a gap between code that looks correct and code that actually is. And that gap isn’t the same size in every language.
AI Can Code in Almost Any Language. That Doesn’t Mean It Understands Them EquallyIt’s worth separating a few things that get lumped together under “AI can code.”
Generating syntactically plausible code just means the output resembles valid code in the target language: correct-looking brackets, correct-looking keywords. Generating compilable code means it actually survives the compiler or interpreter without errors.
Generating functionally correct code means it does what you asked.
Generating idiomatic code means it does it the way an experienced developer in that language actually would, using the right patterns and conventions instead of a technically valid but clumsy translation from some other language’s habits.
Generating maintainable, production-quality code means someone else, or you, six months later, can read it, extend it, and trust it under conditions the original prompt never mentioned.
A model can clear the first bar and completely miss the last one. “The code looks right” is a weak test precisely because it only checks the first, most superficial layer. It’s the layer easiest for a model to get right and the one that tells you the least about whether you should actually use what it gave you.
The Language Matters More Than Most People ThinkProgramming languages aren’t interchangeable skins over the same underlying logic. They come with different type systems, memory models, compiler strictness, standard libraries, ecosystem conventions, and very different levels of representation in the data available to language models. All of that can shape performance, and no single factor explains the whole picture.
Python: The Language AI Usually Feels Most Comfortable WithPython tends to be where AI coding assistants feel most fluent, and there’s a fairly obvious reason for that. It has an enormous public footprint, in tutorials, in open-source repositories, in competitive programming archives, in the kind of educational content that makes up a large share of what these models were trained on. Its syntax is comparatively forgiving, and its dynamic typing means there are fewer strict rules for a model to violate in the first place.
Benchmark evidence backs this up specifically on efficiency, not just correctness. EffiBench-X, a 2025 multi-language benchmark from researchers including King’s College London, measured not just whether generated code works but how efficiently it runs compared to expert human solutions. It found that current models consistently produce more efficient code in Python, Ruby, and JavaScript than in Java, C++, and Go.
One useful example comes from DeepSeek-R1: its Pass@1 correctness was actually very similar on C++ and Python — 75.12% versus 74.64% — while its execution-time efficiency was noticeably better on Python, at 67.30% of the human reference level versus 60.89% on C++.
That distinction matters: getting the answer right and producing an efficient implementation are not the same thing. That’s a useful reminder that “gets the right answer” and “gets it well” are separate questions, and a model can be better at one than the other depending on the language.
None of this means generated Python is automatically good. It just means the odds are better. Python code from an assistant still routinely assumes the wrong version of a library, misses an edge case around empty input or unusual encoding, reaches for a dependency that wasn’t necessary, or swallows an exception in a way that will make debugging painful three weeks from now. Comfort with a language isn’t the same as care.
C and C++: When “Almost Correct” Is Still WrongC and C++ punish a specific kind of mistake that other languages simply don’t allow to happen in the first place: mistakes involving memory that the language trusts you to manage yourself.
Picture asking for a small function that copies a fixed-size buffer. A model might return something that looks completely standard, allocates a buffer, copies into it, and returns it, without accounting for what happens if the source is longer than expected, or forgetting who is responsible for freeing that memory once the caller is done with it. Nothing about that code look wrong on a read-through. It’s the kind of mistake that a compiler often won’t catch, that a casual test often won’t trigger, and that a sanitizer or a genuinely adversarial input eventually will.
A clean compilation is not a guarantee of correctness. In C and C++, code can compile, run normally on one machine, pass a handful of tests, and still contain undefined behavior that appears only under a different compiler, platform, optimization setting, or input.
Rust: Where the Compiler Becomes the Second ReviewerRust is one of the more interesting cases in this entire discussion, and the honest picture is more nuanced than “AI struggles with Rust.”
Rust’s defining features, ownership, borrowing, lifetimes, and a compiler that refuses to build code it considers unsafe, exist specifically to catch the exact category of mistake that quietly slips through in C and C++. Imagine a small example: a function that takes a vector, hands out a reference to one of its elements, and then tries to modify the vector while that reference is still alive.
In most languages, that’s completely normal and nobody blinks. In Rust, the borrow checker will refuse to compile it, because the compiler can’t guarantee the reference stays valid once the underlying data moves. A model unfamiliar with exactly how strict that guarantee is might generate code that looks completely reasonable and gets rejected outright.
That strictness is often mistaken for the language being “hard for AI.” Recent independent benchmarking complicates that story. A comparative analysis published on HackerNoon in early 2026, testing multiple current models on 100 recent problems published between October 2025 and February 2026, specifically chosen to avoid problems the models could have memorized, found that the performance gap between Python and Rust wasn’t statistically significant for the models tested, well within the same range as the Python-to-Java gap.
The much larger, clearly significant gap in that same study showed up with Elixir, a far less represented language, not Rust. That’s a useful corrective to the assumption that Rust is uniquely difficult. It may be more accurate to say Rust is unforgiving rather than poorly supported: the compiler exposes mistakes immediately and refuses to let weak reasoning slide through, which can make failures more visible even when the underlying error rate isn’t dramatically higher than in other mainstream languages.
But Rust exposes a different problem: keeping up with a changing ecosystem. RustEvo², a benchmark specifically designed around Rust API evolution, evaluated 588 API changes across the Rust standard library and third-party crates. The researchers found a substantial knowledge-cutoff effect: models averaged 56.1% success on APIs available before their training cutoff, compared with 32.5% on APIs introduced afterward. Retrieval of current documentation improved performance on those newer APIs by an average of 13.5%.
That matters because it changes the diagnosis. The problem isn’t simply that Rust is “too hard” for AI. Sometimes the model is reasoning incorrectly; sometimes it simply doesn’t have reliable knowledge of the API version you’re asking it to use.
The honest summary: Rust isn’t too hard for AI in some fundamental sense. It’s a language where the compiler acts as an immediate, unforgiving second reviewer, and where a model’s outdated knowledge of a fast-evolving ecosystem shows up faster and more visibly than it would in a language with looser rules.
Java and Other Strongly Typed LanguagesJava sits in a comfortable middle ground for most models. Its verbosity, explicit types, predictable class structures, and well-worn API conventions are heavily represented in public code, and models tend to handle common patterns, standard class definitions, typical interface implementations, familiar boilerplate, quite reliably.
Where it gets shakier is exactly where human developers also find Java tricky: concurrency, where subtle timing assumptions are easy to get wrong; framework-specific behavior in ecosystems like Spring, where conventions matter as much as syntax; generics, where type bounds can get genuinely gnarly; and version drift across API changes, similar in spirit to what RustEvo² documented for Rust. None of this suggests Java ranks definitively below or above Rust or C++ in some universal ordering. Ranking languages on a single scale flattens differences that actually depend heavily on the specific task and model being tested.
The Hidden Problem: AI Loves Popular LanguagesThere’s a subtler issue than raw error rate: models tend to default toward popular languages even when a different one would serve the task better, a pattern visible across the efficiency research already discussed, where scripting languages consistently receive more optimization-aware output than compiled ones.
Picture asking an assistant to design a high-throughput backend service. Python is heavily represented in the model’s training data, so a Python-based suggestion often arrives first and most confidently, complete with a plausible-sounding framework recommendation. But the right engineering call might actually hinge on latency requirements, memory constraints, the deployment environment, or the concurrency model the team already relies on, considerations a model can reason about if asked directly, but won’t automatically foreground on its own.
This is worth sitting with, because it’s not really a language-specific bug. It’s closer to a general pattern: a model tends to predict what’s common in its training data before it evaluates what’s actually optimal for your specific constraints. That’s not a reason to distrust AI-suggested languages outright. It’s a reason to state your actual constraints up front rather than letting the model guess at them from a bare problem description.
The Bigger Problem Isn’t SyntaxUnderneath all of this, the hardest parts of AI-assisted programming usually aren’t syntactic at all. They’re the same things that separate a junior developer from a senior one: choosing the right abstraction for the problem, understanding constraints that were never explicitly stated, picking an algorithm that scales the way the real data will, managing resources correctly over the program’s whole lifetime, anticipating edge cases nobody mentioned, and writing something that still makes sense after requirements inevitably change.
Passing a test suite proves a program behaves correctly on the inputs you thought to test. It says very little about whether the underlying design will hold up as the codebase grows, or whether the next person to touch it will understand why it was written that way.
Why Benchmarks Can Mislead DevelopersIt’s worth being honest about what coding benchmarks actually measure, because headline numbers travel further than their caveats do.
Most function-level benchmarks, the kind that produce the widely quoted pass rates, test short, self-contained problems that look more like interview questions than production work. Real repository-level benchmarks like SWE-bench, which test whether a model can resolve an actual GitHub issue inside an existing codebase, tell a noticeably different story.
SWE-bench Verified scores have climbed into the 70–80% range for top models by early 2026, but a harder variant called SWE-bench Pro, designed specifically to resist the kind of memorization and pattern-matching that inflates scores on well-known benchmarks, shows even frontier models scoring closer to 23%. That’s not a contradiction. It’s a reminder that a benchmark’s difficulty and realism matter enormously, and a single headline percentage rarely tells you which kind of problem it actually measured.
The same caution applies to language comparisons. A benchmark built from competitive programming problems, like EffiBench-X, will surface different strengths and weaknesses than one built from real GitHub issues, like SWE-bench, or one built specifically around a single language’s evolving API surface, like RustEvo². None of these benchmarks is wrong. Each one is measuring a narrower slice of “can this model program” than its headline number implies.
What Developers Should Actually DoA workflow that treats the model as a fast first draft, not a final answer, tends to hold up better across all of this. Specify the exact language version, compiler, or runtime up front. State your real constraints, performance targets, memory limits, concurrency needs, before asking for an implementation, not after. For anything non-trivial, ask for an approach and reasoning before asking for code. Compile immediately. Run the actual tests, not just the ones the model wrote for itself. Read the code you’re about to use, and ask the model to explain any part you don’t fully follow. Check performance and resource usage where it matters, not just functional output. And don’t merge anything you can’t explain to someone else.
A few things are worth doing differently by language. In Python, check dependency versions and edge cases explicitly, since the model’s fluency there can create false confidence. In C and C++, compile with warnings turned all the way up and run sanitizers where you can; assume memory-related mistakes are possible even when the code looks clean. In Rust, read the compiler’s own error messages carefully before asking the assistant to “just fix it,” since the compiler is often diagnosing the actual problem more precisely than a quick re-prompt will. In Java, double-check framework and API versions and think specifically about concurrency, since that’s where subtle correctness issues tend to hide behind code that otherwise looks fine.
The Best Way to Use AI With Difficult LanguagesThe strongest pattern that emerges across all of this is treating the model as a collaborator you can push back on, not an oracle you simply accept.
Instead of “write this Rust function,” something closer to “I think this ownership model is correct because of X, here’s my code, tell me where my reasoning is wrong” gets you a genuinely different kind of answer, one that surfaces your own misunderstanding instead of just papering over it. Instead of “fix this C++ bug,” describing the crash, your working theory of what’s happening, and the relevant code, then asking the model to challenge that diagnosis before proposing a fix, keeps you in the loop on why the bug existed in the first place, not just what patch made it go away.
That distinction matters because it’s the difference between using AI to skip understanding a problem and using it to arrive at understanding faster.
The Real LessonNone of this adds up to “AI is bad at programming.” Current models are, by most available evidence, genuinely strong across a wide range of languages and tasks, and the gap between top models and mainstream languages has been narrowing steadily. What the research does show is that a model’s fluency correlates strongly with how heavily represented a language is in its training data, and that language-specific rules, memory safety, ownership, strict typing, fast-moving APIs, can expose weaknesses that are completely invisible if you’re only judging code by whether it looks right on a first read.
The more a language depends on precise semantics, strict compiler guarantees, careful resource management, or fast-changing ecosystem knowledge, the more dangerous it becomes to mistake plausible code for correct code.
The developer’s job here isn’t disappearing. It’s moving up a level: less about typing out the implementation, more about understanding whether the code, in this language, under these constraints, actually deserves to exist the way it was written.
♦The Programming Languages AI Still Gets Surprisingly Wrong was originally published in Code Like A Girl on Medium, where people are continuing the conversation by highlighting and responding to this story.