ConstructiCat Logo
CodeBust.
Browse section ▾

Reinvented Wheel.

The model hand-writes bespoke code for something a standard library, an existing dependency, or a helper already in the repo already does — adding duplicated, less-tested logic instead of calling what is already there.

##Signs and Symptoms

A reviewer spots Reinvented Wheel when a diff introduces a non-trivial chunk of "from scratch" logic to solve a problem that is already solved — by the language runtime, by a dependency already in package.json, or by a utility that already exists elsewhere in the repo. Common tells:

  • A hand-rolled deepClone, debounce, groupBy, chunk, retry, slugify, deepMerge, or uuid when the runtime or an installed library provides it.
  • A bespoke email/URL/UUID regex instead of a validator that is already a dependency.
  • Custom date math, query-string parsing, or pagination logic that re-implements Intl/URLSearchParams/an ORM feature.
  • Two or three near-identical private helpers across files (the same wheel re-carved per feature) — the duplication that shows up in copy/paste metrics.
  • The reinvented version is subtly wrong: it handles the happy path but misses edge cases the battle-tested original covers.
// AI-written: a bespoke deep clone reinvented inline
function deepClone(obj) {
  if (obj === null || typeof obj !== 'object') return obj;
  if (Array.isArray(obj)) return obj.map(deepClone);
  const out = {};
  for (const k in obj) out[k] = deepClone(obj[k]);
  return out; // silently drops Date, Map, Set, RegExp; loops on cycles
}
// ...even though the runtime ships structuredClone(), AND
// the repo already exports cloneDeep() from src/utils/object.ts

The fastest check: search the repo and node_modules for the capability before accepting the new code. If structuredClone, lodash, date-fns, or a local helper already covers it, the new function is a reinvented wheel.

##Reasons for the Problem

Why models produce it

  • No repo context by default. The model often cannot see your src/utils, your installed dependencies, or your house conventions, so it reaches for the most statistically likely completion: a self-contained inline implementation. It re-derives the wheel because it never saw yours.
  • Next-token locality. LLMs optimize for a locally plausible continuation, not global minimality. Writing function groupBy(...) is a high-probability sequence; pausing to discover that lodash.groupBy is already imported three files over is not something token prediction does.
  • Training incentives reward self-containment. A huge share of training data is tutorials, Stack Overflow answers, and snippets that deliberately show the full implementation. The model learned that "answering well" means emitting complete, standalone code — exactly the wrong instinct inside a mature codebase.
  • Training-cutoff staleness. The model may not know a capability was promoted into the standard library (e.g. structuredClone, Array.prototype.at, Object.groupBy) or that your repo adopted a helper after its cutoff, so it polyfills something that already exists.
  • Sycophancy / least-resistance. Asked to "add X," the model adds X in the most direct way rather than pushing back with "we already have this." It rarely volunteers "you don't need to write this."
  • Over-specification. OX Security's analysis of 300+ repos found AI tends toward "vanilla style" coding that rebuilds common functionality instead of using proven libraries, with narrowly-scoped, non-reusable solutions in roughly 80–90% of cases — each new variation gets fresh code instead of reuse.

Why it hurts

  • Maintainability & tech debt. This is the measurable face of the AI code-quality decline. GitClear's analysis of 211M lines found copy/pasted code rose from 8.3% (2020) to 12.3% (2024), blocks of 5+ duplicated lines grew ~8x in 2024, and "moved" (refactored) lines fell from 24.1% to 9.5% — 2024 was the first year copy/paste exceeded refactoring. Reinvented wheels are how that duplication enters.
  • Correctness. The bespoke version skips the edge cases the mature implementation earned through years of bug reports (timezones, Unicode, cycles, escaping). It looks right and fails in the long tail.
  • Security. Re-rolling crypto, auth, sanitization, or validation instead of a vetted library is how AI-written code "violates engineering best practices" at scale (OX's "Army of Juniors" effect) — vulnerable patterns reach production faster than review can catch them.
  • Review load. Reviewers now have to read, reason about, and test 40 lines of custom logic instead of recognizing one trusted library call — multiplied across every PR.

##Treatment

Review & prompting tactics

  • Give the model the context it lacks. Before generating, point it at your utilities and dependencies: "Reuse helpers from src/utils/* and libraries already in package.json; do not add new ones without asking." Paste the relevant package.json deps and your utils index.
  • Make "search first" a rule. Instruct: "Before writing any helper, check whether the standard library, an existing dependency, or a repo utility already does this; if so, call it." Agentic tools should grep the repo first.
  • Challenge every new private helper in review. For each hand-rolled utility ask: does the runtime do this? does a dependency do this? do we already have this? If yes, it's Reinvented Wheel — replace it.
  • Run the toolchain. A duplication scanner (jscpd / PMD CPD / SonarQube) in CI flags the copy/paste facet automatically; wire it into the gate so re-carved wheels fail the build.

The refactor — this is classic Duplicate Code and Reinvent the Wheel; the fix is Substitute Algorithm (swap the bespoke body for the library/standard call) and, where several copies exist, Extract Function / pull-up to a single shared helper.

// BEFORE — reinvented, partially-correct, duplicated per feature
function deepClone(obj) {
  if (obj === null || typeof obj !== 'object') return obj;
  if (Array.isArray(obj)) return obj.map(deepClone);
  const out = {};
  for (const k in obj) out[k] = deepClone(obj[k]);
  return out;
}
const copy = deepClone(state);
// AFTER — call what already exists (correct edge cases, zero new code)
const copy = structuredClone(state);
// or, if the repo standard is the local helper:
import { cloneDeep } from "@/utils/object";
const copy = cloneDeep(state);

If the wheel was reinvented in several files, delete all copies and route every caller through the single source of truth. Net result: fewer lines, fewer bugs, and the duplication metrics move back in the right direction.

##Detected by

  • jscpd duplicationCopy/paste detection (min-tokens / threshold)
  • PMD cpdCPD (Copy/Paste Detector)
  • SonarQube common-duplicationsDuplicated blocks / duplicated_lines_density