Outdated or Deprecated Pattern.
AI assistants confidently emit APIs, libraries, and idioms that were current years ago but are now deprecated, removed, or superseded, because their parametric knowledge is frozen at a training cutoff and the old pattern is still the statistically most common one in their corpus.
##Signs and Symptoms
A reviewer recognizes this smell when generated code runs but matches the style of a two- or three-year-old tutorial: it calls APIs the framework now warns about, pins old major versions, or reaches for legacy idioms instead of the modern stdlib/syntax. Tell-tale signs:
- Deprecation warnings at install/build/runtime that the model ignored (
DeprecationWarning,[DEP0005],componentWillMount is deprecated). - Idioms that predate the current language baseline:
varoverconst/let, callbacks/.then()chains where the codebase usesasync/await,require()in an ESM project,moment.jsinstead ofIntl/Temporal/date-fns. - Library calls removed in the version you actually have installed: pandas
df.append(...)/.ix[], React legacy lifecycles,datetime.utcnow(), Nodenew Buffer(),url.parse(). - Legacy security defaults that dominate old training data: MD5/SHA1 hashing,
crypto.createCipher, raw string-concatenated SQL. - Reinventing what the platform now ships (hand-rolled
deepClone,groupBy, UUID) instead ofstructuredClone,Object.groupBy,crypto.randomUUID().
// AI-generated: compiles, but every line is a now-deprecated pattern
const crypto = require('crypto'); // ESM project uses import
const buf = new Buffer(userInput); // Buffer() deprecated since Node 6
const id = crypto.createHash('md5') // MD5 deprecated for any security use
.update(token).digest('hex');
const cipher = crypto.createCipher('aes-256-cbc', key); // removed-path API, weak KDF
request(apiUrl, (err, res, body) => { /* ... */ }); // 'request' unmaintained since 2020
##Reasons for the Problem
Why models produce it
- Training-cutoff staleness. An LLM's parametric knowledge is frozen at its cutoff, but libraries keep evolving. Studies measuring this find a 25–38% deprecated-API usage rate across eight Python libraries in LLM completions (Wang et al., ICSE 2025), and even state-of-the-art models score only 48–51% on version-conditioned generation (GitChameleon 2.0). There is no real-time API/version awareness at inference time.
- Next-token frequency favors the old way. The corpus contains years of legacy code, so the statistically most likely completion is often the obsolete one. OX Security notes models default to string concatenation and MD5/SHA1 precisely because those "appear most frequently in training data," and Veracode found models pick the insecure/legacy option ~45% of the time when given a choice (OX Security).
- No repo context. The model usually doesn't know which major version you actually installed, what your lockfile pins, or that
lib/hash.tsalready exists — so it can't target your API surface. Stack Overflow's framing: an agent is "a great pattern recognizer… great code in, great code out" — given no current example, it falls back to the average of its training data (Reyes Q&A). - "Make it work" reward + sycophancy. Models optimize for code that plausibly compiles and satisfies the prompt, not for "uses the current, supported path." The deprecated call still works today, so it looks correct.
Why it hurts
- Maintainability / time-bomb breakage. Deprecated today means removed in the next major — the code works until an upgrade silently breaks it, and these patterns get copy-pasted rather than refactored. GitClear's 211M-line analysis shows copy/pasted lines rose 8.3%→12.3% while refactored ("moved") lines fell 24.1%→9.5% from 2020–2024, with duplicate blocks up ~8x — so a single stale idiom propagates instead of being fixed once (GitClear 2025).
- Correctness. Superseded APIs often have subtly different semantics (
utcnow()returns a naive datetime; legacy lifecycles fire at different times), producing bugs that pass a quick smoke test. - Security. Deprecated crypto (MD5,
createCipher) and outdated package versions carry known CVEs the model never checks against any advisory database — a core driver of the finding that the majority of AI-generated code ships with at least one vulnerability. - Review load & tech debt. Each occurrence is a separate hand-fix, and because the pattern is duplicated rather than centralized, the cost compounds across the codebase.
##Treatment
Review & prompting tactics
- State the versions in the prompt or context. "We're on React 19, pandas 2.2, Node 22 (ESM) — use only APIs current in those versions." Pull the current docs into context (a docs MCP / Context7) instead of trusting parametric memory.
- Force a tool pass. Require the model to run the linter, typechecker, and
npm outdated/pip list --outdated, then fix every deprecation warning before declaring done. Deprecation warnings are the cheapest possible detector — don't let them be ignored. - Require reuse over reinvention. "Use the existing helper in
lib/hash.ts; do not hand-roll hashing." Point at the canonical module so the model doesn't regenerate a legacy version (Duplicate Code). - Codemods over hand-edits. For systematic upgrades, run the official codemods/modernizers rather than asking the model to rewrite by hand:
npx @next/codemod, React codemods,pyupgrade/RuffUPrules. They are deterministic and complete.
The refactor — this is essentially Substitute Algorithm / replace-deprecated-API, and consolidating the duplicated stale calls into one helper applies Extract Function against Duplicate Code:
// Before — deprecated/removed-path APIs
const buf = new Buffer(userInput);
const id = crypto.createHash('md5').update(token).digest('hex');
const cipher = crypto.createCipher('aes-256-cbc', key);
// After — current, supported, security-sane
import crypto from 'node:crypto';
const buf = Buffer.from(userInput); // safe constructor
const id = crypto.createHash('sha256').update(token).digest('hex');
const iv = crypto.randomBytes(16); // explicit IV
const cipher = crypto.createCipheriv('aes-256-cbc', key, iv);
// (for passwords, use argon2/bcrypt — not a raw hash)
For dependencies, the equivalent fix is bumping the pin and letting the codemod migrate call sites, then gating future drift in CI (lint rule below + a Dependabot/Renovate policy) so the model can't silently reintroduce the old pattern.
##Detected by
- ESLint (eslint-plugin-n) n/no-deprecated-api — Disallow deprecated Node.js core APIs (Buffer(), crypto.createCipher, url.parse, etc.)
- typescript-eslint @typescript-eslint/no-deprecated — Flag use of any symbol marked @deprecated in its JSDoc/TS declaration
- ESLint (eslint-plugin-react) react/no-deprecated — Flag deprecated React APIs and lifecycle methods (componentWillMount, etc.)
- SonarQube / SonarSource RSPEC-1874 ("@Deprecated" code should not be used) — Reports usage of code annotated/marked as deprecated
- Ruff pyupgrade (UP) rules — Detects and rewrites outdated/superseded Python idioms to modern equivalents
- Dependabot version-updates — Flags outdated dependency versions (the stale-package facet of this smell)