FaizanAhmedRaza
The Vibe Coding Trap: Why AI-Generated Code Needs an Engineer
Software Engineering4 min readJune 5, 2026

The Vibe Coding Trap: Why AI-Generated Code Needs an Engineer

You can now build a working app in hours with AI. The question isn't whether it works — it's whether it will still work in six months when your users, load, and requirements have all changed.

By Faizan Ahmed Raza

AISoftware EngineeringCode QualityLLMs

A new pattern has emerged in the last 18 months: non-technical founders and product people building full working applications almost entirely with AI assistance — cursor, Claude, Copilot, v0. The apps look great, they function in demos, and they ship in days.

Then reality arrives.

What "Vibe Coding" Actually Produces

The term "vibe coding" — prompting AI to build what you need without deeply understanding the output — describes a real workflow that produces a specific class of artefact: software that works under the conditions it was tested in.

The problems emerge when those conditions change, which happens with every non-trivial application in production.

It works until it needs to change

AI-generated code is optimised to be correct for the prompt. It is not optimised for future maintainability, extension, or modification. The patterns chosen are often whatever the model's training data suggested most frequently for that context — not what's best for your specific system.

Adding a feature to a vibe-coded codebase often requires regenerating large sections, because the structure wasn't designed with extension in mind. Each regeneration has a chance of breaking something that was previously working.

It works until load increases

AI-generated database queries often contain N+1 patterns — a query for every item in a list rather than a single join. In development with 20 rows, this is invisible. In production with 20,000 users, it brings the database to its knees.

Rate limiting, connection pooling, caching, and query optimisation are rarely prompted for and rarely volunteered. They appear in the codebase only if the human building it knows to ask for them.

Real Code Comparison: The AI "Demo" vs. Production Reality

Consider an API route generated by prompting an AI assistant: "Create a Next.js endpoint to fetch team projects and calculate aggregate budget usage."

Here is what the initial vibe-coded endpoint looked like:

// ❌ The Vibe-Coded Version (Functional in demo, catastrophic in production)
export async function GET(request: Request) {
  const { searchParams } = new URL(request.url);
  const teamId = searchParams.get('teamId');

  // Issue 1: Unindexed fetch of ALL records into server memory
  const projects = await db.query(`SELECT * FROM projects WHERE team_id = '${teamId}'`); // SQL Injection risk!

  // Issue 2: Classic N+1 query loop across relations
  const results = [];
  for (const project of projects) {
    const expenses = await db.query(`SELECT amount FROM expenses WHERE project_id = ${project.id}`);
    const totalSpent = expenses.reduce((acc, curr) => acc + curr.amount, 0);
    results.push({ ...project, totalSpent });
  }

  // Issue 3: Unhandled errors crash the serverless function with stack trace leaks
  return Response.json({ success: true, data: results });
}

In a local dev environment with 3 mock projects, this executes in 40ms. But under real production conditions:

  1. Direct string interpolation allows SQL injection.
  2. For a team with 50 projects and 5,000 expenses, it executes 51 separate database roundtrips (the N+1 loop), locking connection pools and taking 3.8 seconds.
  3. Lack of schema validation (zod) allows arbitrary queries that crash backend instances.

Here is how an experienced software engineer refactors that prompt output into production-ready software:

// ✅ The Production-Hardened Version
import { z } from "zod";
import { auth } from "@/lib/auth";
import { db } from "@/lib/db";
import { rateLimiter } from "@/lib/rateLimit";

const querySchema = z.object({
  teamId: z.string().uuid(),
  limit: z.coerce.number().int().min(1).max(50).default(20),
  cursor: z.string().optional(),
});

export async function GET(request: Request) {
  try {
    // 1. Enforce Authentication & Organization Scoping
    const session = await auth();
    if (!session?.userId) {
      return Response.json({ error: "Unauthorized" }, { status: 401 });
    }

    // 2. Prevent DDoS & API abuse via distributed sliding-window rate limiting
    const { success } = await rateLimiter.limit(`team:${session.userId}`);
    if (!success) {
      return Response.json({ error: "Too Many Requests" }, { status: 429 });
    }

    // 3. Strict Input Parsing
    const { searchParams } = new URL(request.url);
    const parsed = querySchema.safeParse(Object.fromEntries(searchParams));
    if (!parsed.success) {
      return Response.json({ error: "Invalid parameters", details: parsed.error.issues }, { status: 400 });
    }

    // 4. Single-Query Aggregation with Parameterized Binding & Row-Level Security
    const aggregatedProjects = await db.query(
      `
      SELECT 
        p.id, 
        p.name, 
        p.status,
        COALESCE(SUM(e.amount), 0) AS total_spent
      FROM projects p
      LEFT JOIN expenses e ON e.project_id = p.id
      WHERE p.team_id = $1 AND p.org_id = $2
      GROUP BY p.id
      ORDER BY p.created_at DESC
      LIMIT $3
      `,
      [parsed.data.teamId, session.orgId, parsed.data.limit]
    );

    return Response.json({ success: true, data: aggregatedProjects });
  } catch (err) {
    // 5. Sanitized error logging without leaking infrastructure details
    console.error("[API_ERROR] Team projects aggregation failed:", err);
    return Response.json({ error: "Internal Server Error" }, { status: 500 });
  }
}

The Production Readiness Scorecard

Before shipping AI-generated code to paying customers or enterprise partners, grade the codebase against this production matrix:

Engineering DimensionVibe-Coded PrototypeProduction Standard
Authentication & AuthZHardcoded checks or happy-path sessionsRow-level security (RLS), RBAC, session revocation
Database PerformanceN+1 iterative queries, SELECT *Single aggregation queries, compound indexing, connection pooling
Data ValidationLoose TypeScript any or no runtime schemaZod / Pydantic schema validation on all inputs & outputs
Resilience & FaultsUncaught exceptions with raw stack dumpsStructured error logging, circuit breakers, graceful fallbacks
Concurrency & LimitsUnbounded API routes vulnerable to scrapingDistributed Redis sliding-window rate limiters
Observabilityconsole.log() statementsOpenTelemetry distributed tracing, APM, health check endpoints

The Asymmetry

This is not an argument against AI-assisted development. It's one of the most powerful productivity tools available. I use it constantly.

The argument is about the role of engineering judgement in the process.

Experienced engineers use AI to accelerate work they understand. They review the output against a mental model of what correct, maintainable, secure code looks like. They accept what's right, reject what's wrong, and direct the AI toward better solutions.

Vibe coding uses AI as a replacement for that judgement. The output is accepted if it runs, not if it's correct. The human doesn't have the model of the system needed to evaluate what they're accepting.

The asymmetry: the AI does not get worse at producing code you don't understand. It produces the same code regardless. The difference is entirely in whether there's a competent engineer in the loop to catch the problems.

When Vibe Coding Is Fine

There are real use cases where this approach is entirely appropriate:

  • Prototypes and demos: functionality over quality, lifespan of weeks
  • Personal tools: low stakes, single user, maintainer is also the user
  • Exploration: trying something to see if it's worth building properly
  • Templates and boilerplate: well-understood patterns where AI output is predictably good

If you're building for revenue, users, or longevity — the cost of the engineering review is smaller than the cost of the production incidents, security breaches, and rewrites you're deferring.

The Practical Model

The highest-leverage use of AI in software development is not replacing engineers — it's dramatically multiplying what engineers can produce.

A good engineer with strong AI tooling:

  • Produces 2–4× more code per day
  • Spends less time on boilerplate and more time on design
  • Has more capacity for the hard problems: architecture, security, performance

A non-engineer with strong AI tooling:

  • Builds things that work in demos
  • Creates a technical foundation that becomes increasingly expensive to maintain

The second scenario has a place in the ecosystem. But if your product is your business, get an engineer to review what's been built — or better, to drive the build with AI as the accelerator. That's what I do for early-stage products and teams.

Want to work together?

I help companies build AI-powered products and automate complex workflows.

More Insights