Advertisement

Of everything an AI coding assistant gets wrong, the invented API is the one most likely to survive to production. Not because it is hard to detect — a missing function fails immediately — but because of what it does to the reviewer. Wrong logic looks wrong if you read it carefully. A call to a method that sounds exactly like the method that should exist looks right to everyone, including the person who knows the library.

It is worth understanding why the model does this, because the reason tells you where it will happen most and what kind of check actually catches it.

This is not an argument against using these tools. It is an argument for knowing their specific failure shape, which is what lets you use them fast and safely at the same time.

Why a plausible name is the most likely output

A language model produces the most likely continuation of the text so far, trained across an enormous amount of public code. Libraries in a given ecosystem name things in consistent ways: a thing that fetches by identifier is often getById, a thing that converts is often toSomething, options usually go in a trailing object. Those patterns are strong, and the model has learned them thoroughly.

So when a real method for a task is missing from what the model can recall, the most likely next tokens are still a name shaped like that library names things. The output is not a random guess — it is the name the library would most plausibly have used, which is exactly what makes it convincing. The model is not lying; it has no mechanism for distinguishing a name it has seen from a name that fits the pattern.

This predicts where it happens most: on smaller or newer libraries where there is less material, on recently changed APIs where the model has learned an older shape, and at the boundary between two similar libraries whose conventions the model may blend.

Advertisement

Why review does not catch it

Code review is good at catching logic that does not follow and bad at catching names that do. A reviewer reading a call to a well-named method makes the same inference the model did: this is what the method would be called, so this is the method. Familiarity makes it worse rather than better — someone who uses the library daily has the strongest expectation about what it should contain.

The type checker is the natural defence and it has a specific hole: it only helps where types are known. In a strictly typed codebase against a fully typed library, an invented method fails to compile, and the problem is essentially solved. Where the value is untyped, or dynamically constructed, or the library ships no types, nothing objects until runtime.

The other hole is coverage. Generated code frequently lands in error branches, retry paths and edge cases — exactly the places tests are thinnest, and exactly the places nobody exercises by hand. A hallucinated call in a catch block can sit in main for months and then fire on the day something else already went wrong.

The checks that actually work

The most effective habit is the cheapest: for every unfamiliar call in generated code, look it up in the real documentation before accepting it. Not a search that surfaces a blog post — the library's own reference, or its type definitions, or its source. This takes seconds per call and it is the only check that catches the case where everything else passes.

Structurally, three things pay for themselves. Run the code, including the branches the assistant wrote, before merging — a generated retry path that never executes is not reviewed, it is only read. Prefer typed interfaces at the boundary, so the checker can object. And keep the assistant working in small pieces you can verify, rather than accepting a large block whose middle nobody reads.

A useful tell: an invented method often has a slightly better name than the real one. Real APIs carry history — awkward names kept for compatibility, an inconsistent argument order, a verb that made sense in an older version. When generated code reads more cleanly than the library usually does, that is worth a lookup.

The near miss is worth naming separately from the invention, because it fails differently. A method that genuinely existed two major versions ago will be missing today, and the assistant produced it from material written when it was real. Here the documentation search succeeds and misleads, because it finds the old page. Check that what you are reading matches the version in your lockfile, not merely that it exists somewhere.

Advertisement

The line worth holding

The rule that survives all of this is simple to state and genuinely hard to keep: do not merge code you could not have written yourself. Not "would not have" — plenty of generated code is better than what you would have typed. Could not: if you cannot explain what a line does and why it is correct, you cannot debug it, and it will eventually need debugging, probably at the worst time.

Applied honestly this does not slow you down much, because it does not require writing the code yourself. It requires reading it properly once, which is a fraction of the time the tool saved. The developers who get the most out of these assistants are not the ones who trust them least or most, but the ones who read the output at the speed it deserves.

And it is worth being clear about the upside, because the failure mode is not an argument against the tool. For repetitive transformations, first-draft tests, explaining an unfamiliar codebase, or sketching an approach before committing to it, an assistant is genuinely fast and genuinely good. The invented function is a known, bounded, checkable risk — which is the best kind to have.

Why this specific failure survives code review more often than an obviously broken suggestion would

A hallucinated function call that references a plausible, well-named, and stylistically consistent API is considerably harder for a human reviewer to catch by reading alone than an obviously malformed or nonsensical piece of code, since the hallucinated reference looks exactly like a real, legitimate call to an unfamiliar part of a library the reviewer simply has not personally used before — this is precisely why this specific failure mode disproportionately survives review compared to more visibly broken mistakes, and why the verification habits this article describes matter more here than ordinary code-quality scrutiny alone would catch.

Advertisement

Why a failing build is the fastest, cheapest way this specific mistake gets caught in practice

A hallucinated function or method that genuinely does not exist typically fails immediately and loudly at build or run time in a statically checked language, or the very first time the affected code path actually executes in a dynamically typed one — this is precisely why running the actual code, not merely reading it, is disproportionately effective specifically against this failure mode compared to a subtler logic bug that might execute successfully while still producing a quietly wrong result.

Why this problem is worse for less popular libraries with sparser training-data representation

A widely used, thoroughly documented library's actual API surface is represented so extensively across a model's training data that hallucinating a plausible but nonexistent method for it is comparatively rare, while a niche, sparsely documented library gives the model far less real signal to draw from, making a fluent, plausible-sounding but entirely invented method name considerably more likely — this is the same underlying training-data-representation mechanism discussed at greater length in this library's dedicated hallucination article, showing up here specifically in the context of a single invented function call.

Why an automated check for unresolved imports or undefined references closes this gap systematically

Rather than relying purely on a human reviewer noticing an unfamiliar function call and deciding to verify it, configuring a linter or type checker to flag any unresolved import or undefined reference automatically catches this specific failure mode mechanically, every time, regardless of how convincing or unfamiliar the hallucinated reference happens to look to a human eye — this is exactly the kind of gap best closed by tooling enforced consistently, rather than left entirely to an individual reviewer's own vigilance in any single instance.

Why the problem gets worse with obscure libraries

Hallucination rates are not uniform across libraries — they track training data volume closely, and that has a specific, predictable consequence: the more niche or newly released a library is, the more likely an AI assistant is to invent a plausible-looking method for it that doesn't exist, because the model has seen comparatively little real code using that library and is filling gaps with pattern-matched guesses from more common, similarly-shaped libraries it has seen far more of.

This creates a paradox for teams evaluating a new or small library: the moments when you most need accurate guidance — working with something unfamiliar — are exactly the moments when an AI assistant's suggestions are least reliable. The practical response is to raise your skepticism specifically, not uniformly: treat suggestions involving a library with sparse public usage as needing more verification than suggestions involving something enormously well-documented like a core language API, rather than applying one fixed level of trust everywhere.

Verification habits that catch it before code review

The single fastest, cheapest check is one many developers skip out of habit: before accepting a suggested method call, jump to that library's actual type definitions or documentation and confirm the method is real and takes the arguments you were just shown. This takes seconds when your editor supports go-to-definition and immediately turns a maybe into a certainty, rather than deferring that certainty to whenever the code is first run or, worse, to a reviewer who assumes the author already checked.

Beyond manual lookups, static analysis is unusually well suited to this exact failure mode: a linter or type checker configured to flag unresolved imports and unknown members will catch a hallucinated method call immediately, with no need for the code to execute at all. Teams that treat a clean lint and type-check run as a hard requirement before merge get this category of bug caught mechanically and consistently, instead of relying on any one person's memory of the library's real surface area.

Building the habit of running the code immediately

Because a failing build or a runtime exception is the fastest and cheapest way to catch a hallucinated call, the practical habit that pays off most is running the new code as soon as it is written rather than reading several more suggestions first and running everything together at the end. Running each suggestion as it lands isolates exactly which change introduced the failure, instead of leaving you to guess among a larger batch of unreviewed, unrun code.