On this page
  1. The problem: AI writes code fast, debt accumulates faster
  2. The three types of AI tech debt
    1. 1. Duplication: the same logic in multiple places
    2. 2. Dead code: functions with no callers
    3. 3. Over-engineering: complexity the feature doesn’t need
  3. The review system: catch debt before it’s committed
    1. Check 1: Does this exist elsewhere?
    2. Check 2: Is this code actually used?
    3. Check 3: Is this simpler than it looks?
  4. The preventive measures
    1. Write a .cursorrules or .aidigest file
    2. Review before committing, always
  5. The cleanup cadence
  6. Checklist
  7. FAQ
    1. How fast does AI-generated tech debt accumulate?
    2. Can’t I just ask the AI to not duplicate code?
    3. What’s the difference between AI tech debt and regular tech debt?
  8. Related topics
  9. Sources
how-to

How to Work With an AI Coding Assistant Without Creating a Mountain of Tech Debt

AI assistants generate code fast — and generate tech debt faster. Duplicated logic, dead code, over-engineered abstractions. Here's the system for catching these before they accumulate: review, deduplicate, simplify, delete.

Quick answer

  • AI assistants generate code fast but don’t consider your existing codebase. The result: duplicated logic, dead code, and over-engineered solutions.
  • The system: after every AI-generated feature, check for duplication, dead code, and unnecessary complexity — before committing.
  • AI-generated tech debt is worse than hand-written tech debt because you don’t understand it. Catch it immediately or it’s permanent.

The problem: AI writes code fast, debt accumulates faster

AI coding assistants have one speed: full throttle. They generate 200 lines for a feature that needed 50. They duplicate a helper function because they can’t see it already exists. They leave dead code from “cleanup” passes that removed the caller but not the function.

And you don’t understand any of it. AI-generated code you’ve reviewed is debt you can manage. AI-generated code you haven’t reviewed is debt that owns you.

The three types of AI tech debt

1. Duplication: the same logic in multiple places

# AI generated in users.py:
def validate_email(email):
    return "@" in email and "." in email.split("@")[1]

# AI generated in orders.py (didn't know users.py exists):
def check_email_format(email):
    if "@" not in email:
        return False
    if "." not in email.split("@")[1]:
        return False
    return True

Two implementations of email validation. Different function names, slightly different logic, same purpose. When the validation rule changes, one gets updated and the other doesn’t — a bug waiting to happen.

For a detailed guide, see: How to Stop AI Assistants from Duplicating Code.

2. Dead code: functions with no callers

# AI generated this during a "refactor" pass:
def calculate_discount_legacy(order, rate):
    return order.total * rate

# The caller was removed in the next commit. This function lives on forever.

Dead code is dangerous because the AI reads it as context. It sees calculate_discount_legacy, assumes it’s important, and generates new code that depends on it — creating a dependency on dead code.

For a full guide, see: How to Remove Dead Code Your AI Left Behind.

3. Over-engineering: complexity the feature doesn’t need

# AI generated for "add user registration":
class UserRegistrationFactory:
    def __init__(self, validator, hasher, mailer, logger):
        self.validator = validator
        self.hasher = hasher
        self.mailer = mailer
        self.logger = logger

    def create_user(self, email, password):
        self.validator.validate(email, password)
        hashed = self.hasher.hash(password)
        user = User(email=email, password_hash=hashed)
        self.mailer.send_welcome(user)
        self.logger.info(f"user created: {user.id}")
        return user

# Needed: one function with 10 lines. Generated: factory pattern with 4 injected dependencies.

The AI defaults to patterns it saw in production codebases — patterns designed for scale you don’t have. For a guide on simplifying this, see: How to Simplify Overly Complex AI-Generated Functions.

The review system: catch debt before it’s committed

After every AI-generated feature, before committing, check:

Check 1: Does this exist elsewhere?

# Search for similar function names
grep -r "def validate_email" src/
grep -r "def check_email" src/
grep -r "def.*email.*valid" src/

# Found a duplicate? Extract the shared version and delete the copy.

Check 2: Is this code actually used?

# Find all callers of a function
grep -r "calculate_discount_legacy" src/

# No results? Delete it. If tests reference it, those are dead tests — delete them too.

Most IDEs can show you callers directly (Ctrl+Click or F12 on the function name). Use it. If nothing calls it, it’s dead.

Check 3: Is this simpler than it looks?

Read the AI’s code and ask: “What does this actually do?” If the answer is simple but the code is complex, simplify it.

# AI generated (complex):
def is_user_allowed(user, resource):
    if user.role == "admin":
        return True
    elif user.role == "manager":
        if resource.owner_id == user.id or resource.department == user.department:
            return True
    elif user.role == "member":
        if resource.owner_id == user.id:
            return True
    return False

# Simplify (same logic, half the lines):
def is_user_allowed(user, resource):
    if user.role == "admin":
        return True
    if user.role == "manager" and resource.department == user.department:
        return True
    return resource.owner_id == user.id

The AI generates correct but verbose code — it’s trained on code where verbosity was confused with thoroughness.

The preventive measures

Write a .cursorrules or .aidigest file

Give the AI standing instructions about your codebase:

# .aidigest — standing instructions for AI assistants

- Don't duplicate: before writing a function, check if it already exists.
- Don't over-engineer: use the simplest pattern that works. No factory classes
  for single-implementation interfaces. No abstract base classes unless there
  are at least 2 implementations.
- Delete dead code: when modifying a function, check its callers. If you're
  removing the last caller, delete the function.
- Keep functions under 30 lines. If a function grows beyond that, it's doing
  too much — suggest splitting it.

Review before committing, always

The only reliable defense against AI tech debt is human review. You don’t need to understand every line — but you do need to:

  1. Read function names — do any look like duplicates?
  2. Check for functions with no callers
  3. If the code looks more complex than the problem, it is

The cleanup cadence

Don’t try to fix all debt at once. Do a focused cleanup pass weekly:

  • 5 minutes: grep for duplicate function names
  • 5 minutes: check for dead code (functions with no callers)
  • 10 minutes: simplify the most complex function you touched that week

Over a month, this removes the majority of AI-generated debt before it calcifies.

Where this bites vibecoders

The AI generates 200 lines for a feature. The vibecoder sees it work, commits it. Repeat 20 times. Now there’s 4,000 lines of AI code, ~1,200 of which is duplication or dead code. The AI’s context window is filled with noise, so every new feature generates worse code. The fix is the 3-check system applied to every commit: duplicate? dead? over-complex? Three checks, 60 seconds, prevents the spiral.

Checklist

  • After every AI-generated feature: check for duplication, dead code, over-complexity
  • Use grep or IDE to find callers before deleting or modifying functions
  • Extract shared logic immediately — don’t wait for the “refactor later” that never comes
  • Write standing instructions (cursorrules/aidigest) for your AI assistant
  • Weekly 20-minute cleanup pass on the codebase

FAQ

How fast does AI-generated tech debt accumulate?

Fast. One feature request = 200 lines of AI code. Five features = 1,000 lines. Without review, ~30% of that is duplication or dead code. After 20 features, you have ~600 lines of unnecessary code that the AI now reads as context for every new feature — making every subsequent feature harder to generate correctly.

Can’t I just ask the AI to not duplicate code?

You can, and it helps, but it’s not reliable. The AI’s context window can’t see your entire codebase, so it doesn’t know a function already exists in another file. The fix isn’t a better prompt — it’s catching duplication in review and extracting it.

What’s the difference between AI tech debt and regular tech debt?

Regular tech debt is a choice you made knowingly (“we’ll refactor after launch”). AI tech debt is accidental — code you didn’t know was generated, patterns you didn’t ask for, abstractions you don’t understand. It’s harder to fix because you didn’t write it and don’t know why it exists.


Sources

Share: