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, oruuidwhen 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 thatlodash.groupByis 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 inpackage.json; do not add new ones without asking." Paste the relevantpackage.jsondeps 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 duplication — Copy/paste detection (min-tokens / threshold)
- PMD cpd — CPD (Copy/Paste Detector)
- SonarQube common-duplications — Duplicated blocks / duplicated_lines_density