ConstructiCat Logo
CodeBust.
Browse section ▾

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: var over const/let, callbacks/.then() chains where the codebase uses async/await, require() in an ESM project, moment.js instead of Intl/Temporal/date-fns.
  • Library calls removed in the version you actually have installed: pandas df.append(...) / .ix[], React legacy lifecycles, datetime.utcnow(), Node new 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 of structuredClone, 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.ts already 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/Ruff UP rules. 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