Quick Answer
AI coding tools like Cursor, Bolt, and Lovable routinely generate functions that are 100–300 lines long with 5+ levels of nesting and 8+ parameters. These "god functions" are hard to test, impossible to reuse, and break in unpredictable ways. The fix is to apply the single-responsibility principle: one function does one thing, and that thing can be described in a single sentence.
The God Function Problem in AI-Generated Code
A god function is a function that knows too much and does too much. It handles input validation, database calls, business logic, error formatting, and response shaping - all in sequence, all in the same function body. When you read through vibe-coded apps built with Cursor or Lovable, these functions appear constantly.
The reason is structural. AI models generate code to satisfy the full prompt in one pass. "Handle user registration" becomes a single function that checks if the email exists, hashes the password, creates the user, sends a welcome email, logs the event, and returns a formatted response - 180 lines of sequential, deeply-nested code. It works. It ships. It becomes unmaintainable in months.
According to SonarSource's analysis of over 500,000 open-source projects, functions exceeding 80 lines account for 62% of all critical bugs found in JavaScript codebases. The correlation between function length and defect density is not coincidental - it is structural. Longer functions have more execution paths, more shared state, and more places where an assumption can quietly break.
How AI Generates Complexity: A Real Example
Ask Bolt or Cursor to "create a checkout handler" and you are likely to get something like this:
// ❌ BAD - God function: 1 function, 7 responsibilities
async function handleCheckout(req, res) {
try {
const { userId, cartItems, couponCode, shippingAddress } = req.body;
// Validate inputs (responsibility 1)
if (!userId || !cartItems || cartItems.length === 0) {
return res.status(400).json({ error: 'Invalid input' });
}
if (!shippingAddress || !shippingAddress.zip) {
return res.status(400).json({ error: 'Missing address' });
}
// Calculate price (responsibility 2)
let total = 0;
for (const item of cartItems) {
const product = await db.products.findUnique({ where: { id: item.productId } });
if (!product) return res.status(404).json({ error: `Product ${item.productId} not found` });
total += product.price * item.quantity;
if (product.stock < item.quantity) {
return res.status(400).json({ error: `${product.name} out of stock` });
}
}
// Apply coupon (responsibility 3)
if (couponCode) {
const coupon = await db.coupons.findUnique({ where: { code: couponCode } });
if (coupon && coupon.active && new Date() < new Date(coupon.expiresAt)) {
total = total * (1 - coupon.discount);
}
}
// Charge Stripe (responsibility 4)
const paymentIntent = await stripe.paymentIntents.create({
amount: Math.round(total * 100),
currency: 'usd',
customer: userId,
metadata: { cartItems: JSON.stringify(cartItems) },
});
// Create order (responsibility 5)
const order = await db.orders.create({
data: {
userId,
total,
status: 'pending',
stripePaymentIntentId: paymentIntent.id,
items: { create: cartItems.map(i => ({ productId: i.productId, quantity: i.quantity })) },
shippingAddress: JSON.stringify(shippingAddress),
},
});
// Update stock (responsibility 6)
for (const item of cartItems) {
await db.products.update({
where: { id: item.productId },
data: { stock: { decrement: item.quantity } },
});
}
// Send confirmation email (responsibility 7)
await sendEmail({
to: req.user.email,
subject: 'Order confirmed',
body: `Your order #${order.id} has been placed.`,
});
return res.json({ orderId: order.id, clientSecret: paymentIntent.client_secret });
} catch (err) {
console.error(err);
return res.status(500).json({ error: 'Checkout failed' });
}
}
This function has 7 distinct responsibilities, 3 levels of nesting, and is impossible to unit-test in isolation. Testing the coupon logic requires setting up a Stripe mock, a database mock, and a mail server mock simultaneously.
Why Deep Nesting Is the Most Dangerous Symptom
Function length is a proxy metric. The root issue is cognitive complexity - how hard it is to hold the function's logic in your head. SonarSource's cognitive complexity metric penalizes nesting harder than length. A function with 5 levels of nesting scores as complex as a 200-line flat function.
Deep nesting in AI code typically takes one of two forms. The first is the validation pyramid: each validation check adds another level of indentation, pushing the happy path to column 40+. The second is the callback/promise staircase: nested async calls, each dependent on the last, forming a diagonal line of code that moves rightward.
The DORA 2024 State of DevOps report found that teams with high code complexity scores had 4.5x more deployment failures than teams with low complexity. Complexity is not an aesthetic concern - it directly predicts production incidents.
The Refactored Version: One Function, One Job
// ✅ GOOD - Each function has one responsibility
async function validateCartItems(cartItems) {
const products = [];
for (const item of cartItems) {
const product = await db.products.findUnique({ where: { id: item.productId } });
if (!product) throw new Error(`Product ${item.productId} not found`);
if (product.stock < item.quantity) throw new Error(`${product.name} out of stock`);
products.push({ ...product, requestedQty: item.quantity });
}
return products;
}
async function calculateTotal(products, couponCode) {
let total = products.reduce((sum, p) => sum + p.price * p.requestedQty, 0);
if (couponCode) {
const coupon = await db.coupons.findUnique({ where: { code: couponCode } });
if (coupon?.active && new Date() < new Date(coupon.expiresAt)) {
total = total * (1 - coupon.discount);
}
}
return total;
}
async function chargeAndCreateOrder(userId, total, cartItems, shippingAddress) {
const paymentIntent = await stripe.paymentIntents.create({
amount: Math.round(total * 100),
currency: 'usd',
customer: userId,
});
return db.orders.create({
data: {
userId, total,
status: 'pending',
stripePaymentIntentId: paymentIntent.id,
items: { create: cartItems.map(i => ({ productId: i.productId, quantity: i.quantity })) },
shippingAddress: JSON.stringify(shippingAddress),
},
});
}
// Orchestrator - thin, readable, testable
async function handleCheckout(req, res) {
const { userId, cartItems, couponCode, shippingAddress } = req.body;
if (!userId || !cartItems?.length || !shippingAddress?.zip) {
return res.status(400).json({ error: 'Invalid input' });
}
try {
const products = await validateCartItems(cartItems);
const total = await calculateTotal(products, couponCode);
const order = await chargeAndCreateOrder(userId, total, cartItems, shippingAddress);
await decrementStock(cartItems);
await sendOrderConfirmation(req.user.email, order.id);
return res.json({ orderId: order.id });
} catch (err) {
return res.status(400).json({ error: err.message });
}
}
Each extracted function can now be tested independently. calculateTotal can be tested with no database and no Stripe. validateCartItems can be tested against a mock database. The orchestrator reads like a specification of the checkout process.
Three Metrics That Flag God Functions
When reviewing AI-generated code, apply these three checks to every function:
| Metric | Warning threshold | Danger threshold | Tool to measure |
|---|---|---|---|
| Lines of code | > 50 lines | > 100 lines | ESLint max-lines-per-function |
| Nesting depth | > 3 levels | > 5 levels | ESLint max-depth |
| Parameter count | > 4 params | > 7 params | ESLint max-params |
| Cyclomatic complexity | > 10 | > 20 | ESLint complexity |
| Cognitive complexity | > 15 | > 25 | SonarSource plugin |
How to Fix God Functions in Your Codebase
Refactoring god functions is not a single-session task. Apply this approach iteratively:
- Write characterization tests first. Before touching a god function, write tests that document its current behavior. This gives you a safety net for refactoring.
- Extract by responsibility, not by line count. Do not split at arbitrary line numbers. Find coherent groups of lines that represent a single responsibility and extract those into named functions.
- Name functions after their intention. A function named
calculateDiscountedTotalis self-documenting. A function namedprocessDatais not. - Replace parameter lists with objects. If a function takes more than 4 parameters, group them into a typed object. This makes call sites readable and reduces the number of arguments to track.
Tools like VibeDoctor (vibedoctor.io) automatically scan your codebase for overly complex functions and flag specific file paths and line numbers where god functions exist. Free to sign up.
FAQ
Is there a hard rule on how long a function should be?
Not a universal one, but most style guides converge around 20–50 lines as a practical upper bound. Robert Martin's Clean Code suggests functions should "do one thing" - if you can extract a named function that does something coherent, you should. Length is a symptom; responsibility count is the disease.
Why do AI tools generate god functions instead of small functions?
AI models satisfy the prompt constraint: make this feature work. Generating a single function achieves that. The model has no incentive to consider maintainability, testability, or the cost of future changes. This is a known limitation of prompt-to-code generation.
How do I prevent Cursor or Lovable from generating god functions?
Add explicit instructions to your system prompt or Cursor rules file: "Each function must do one thing. Functions over 40 lines must be refactored. Nest no more than 3 levels deep. Use named helper functions for each logical step." Models follow style constraints when they are explicit.
Does splitting functions affect performance?
In virtually all cases, no. JavaScript engines inline small functions during JIT compilation. The overhead of a function call is negligible. The performance cost of unmaintainable code - in developer time and bug rate - vastly exceeds any micro-optimization benefit.
What is the difference between cyclomatic complexity and cognitive complexity?
Cyclomatic complexity counts the number of independent paths through a function - each if, for, while, case, and ternary adds one. Cognitive complexity, introduced by SonarSource, additionally penalizes nesting: a deeply nested if scores higher than a flat one. For AI-generated code, cognitive complexity is the more useful metric.