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 or deprecated in the version you actually have installed: removed — pandas
df.append(...)(gone since pandas 2.0) and.ix[](gone since 1.0); deprecated but still present — React legacy lifecycles,datetime.utcnow()(deprecated since Python 3.12), 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 on 328 version-conditioned problems, enterprise models reach baseline success rates of only 48–51% (GitChameleon 2.0, ACL 2026). 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 that "String concatenation appears most frequently in training data" and that "MD5 appears frequently in hashing examples throughout its training corpus" (OX Security), and Veracode found that, when given a choice between a secure and an insecure method, models chose the insecure one 45% of the time (Veracode 2025 GenAI Code Security Report).
- 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 the API of your versions. Eno Reyes of Factory calls an agent "a great pattern recognizer" — "so great code in the pattern means great code out" — and given no current example to match, it falls back to the average of its training data (Stack Overflow Q&A with Eno Reyes). - "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 2025 analysis of 211 million changed lines (2020–2024) found that refactoring-associated ("moved") lines fell from 25% of changed lines in 2021 to under 10% in 2024, while copy/pasted lines rose from 8.3% to 12.3% over the same period; 2024 was the first year copy/pasted lines exceeded moved lines, and code blocks with five or more duplicated lines grew roughly eightfold during 2024 (GitClear 2025). A single stale idiom therefore propagates instead of being fixed once.
- 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 — and in Veracode's 2025 testing, AI-generated code introduced security vulnerabilities in 45% of cases (Veracode). - 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 — replacing a deprecated API with its supported successor is essentially Substitute Algorithm, and consolidating the duplicated stale calls into one helper applies Extract Method 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 — Reports usage of code annotated/marked as deprecated — "@Deprecated" code should not be used
- Ruff pyupgrade (UP) — Detects and rewrites outdated/superseded Python idioms to modern equivalents
- Dependabot version-updates — Flags outdated dependency versions (the stale-package facet of this smell)