The AI Cargo Cult: Are You Learning to Code, or Just to Prompt?
The AI Cargo Cult: Are You Learning to Code, or Just to Prompt?
Years ago, stuck on a nasty bug with a deadline breathing down my neck, I found a snippet on Stack Overflow. Copied it. Pasted it. It worked. I committed and basked in the hollow glow of a closed ticket.
Two weeks later, that snippet brought down staging. Since I never understood why it worked, I had zero ability to fix it when it broke. A black box I'd wired into the heart of our system.
Today, we have something far more powerful than Stack Overflow snippets. AI coding assistants can generate entire applications from a few lines of prose. And I see developers, especially those still building foundational skills, making my old mistake at industrial scale. They're not copying snippets. They're outsourcing the entire thinking process.
Anthropic ran a study that confirmed what I'd been observing. The developers who offloaded everything to the AI finished tasks faster than anyone. But when quizzed on the concepts of the library they'd just "used," they scored 17% lower than the control group who only had documentation.
They built a cargo cult. They performed the rituals of coding without grasping the principles underneath.
The delegation trap
The pattern looks like this:
- You hit a problem slightly outside your comfort zone.
- You write a prompt: "Fix this" or "Write a function that does X."
- The AI returns working code.
- You paste it in, see green, move on. The dopamine hit arrives, but the learning circuit in your brain never fires.
When the AI's solution is perfect, you learn nothing. When it's subtly wrong (and it often is), you lack the foundation to spot the error.
Here's the part that stuck with me from the study: the non-AI group encountered three times as many errors. That wasn't a sign of failure. It was the mechanism of their success. Every error forced a learning event.
The approach that actually works
The highest-performing group in the study didn't avoid the AI. They used it constantly. But they used it differently.
They refused to delegate understanding. They treated the AI as a pair programmer to interrogate, not an oracle to obey. They asked why instead of what. They proposed solutions and asked for critiques. They demanded concept explanations before asking for code.
The difference is between these two approaches:
Delegation mode (weak)
"Write me a TypeScript function using Zod to validate a user object. Name (string, min 3), email (valid email), optional age (number, min 18)."
You get perfect code. You paste it. You learned nothing about how Zod handles schemas, inference, or error formatting.
Dialogue mode (strong)
"I'm new to Zod. What's its core philosophy compared to Joi or class-validator? What does 'schema-first' mean?"
"Show me the simplest possible schema: validating that a value is a non-empty string."
"I'll write my user schema myself. Here's my attempt. Is this idiomatic?"
import { z } from 'zod';
const UserSchema = z.object({
name: z.string().min(3),
email: z.string().email(),
age: z.number().min(18).optional()
});"How do I actually use this? Explain
parsevssafeParseand their error handling differences."
"What is
z.infer? How does it derive a TypeScript type from a schema?"
After this conversation, you own the knowledge. The final code might look identical, but what's in your head is completely different:
import { z } from 'zod';
const UserSchema = z.object({
name: z.string().min(3, { message: "Name must be at least 3 characters long." }),
email: z.string().email({ message: "Invalid email format." }),
age: z.number().min(18, { message: "You must be 18 or older." }).optional(),
});
export type User = z.infer<typeof UserSchema>;
export function validateUser(data: unknown): { success: boolean; data?: User; error?: z.ZodError } {
const result = UserSchema.safeParse(data);
if (result.success) {
return { success: true, data: result.data };
}
return { success: false, error: result.error };
}You chose safeParse deliberately because you understood the difference. You used z.infer because you understood what it does.
Same pattern applied to debugging
"My React component gives a memory leak warning on unmount. Fix it" gets you a patch. An AbortController or an isMounted flag. Problem gone, but you never understood the underlying race condition between async operations and the component lifecycle.
The better approach:
"Explain the lifecycle of a
useEffectcleanup function, specifically how it relates to async operations."
"What's an
AbortController? How does passing a signal tofetchprevent the race condition?"
Now when the next async bug hits (it will), you can diagnose it from first principles:
import React, { useState, useEffect } from 'react';
interface Post {
id: number;
title: string;
}
const PostViewer = ({ postId }: { postId: number }) => {
const [post, setPost] = useState<Post | null>(null);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
const controller = new AbortController();
const fetchPost = async () => {
try {
const response = await fetch(`https://api.example.com/posts/${postId}`, {
signal: controller.signal,
});
if (!response.ok) throw new Error('Network response was not ok');
const data: Post = await response.json();
setPost(data);
} catch (err: any) {
if (err.name === 'AbortError') {
console.log('Fetch aborted');
} else {
setError(err.message);
}
}
};
fetchPost();
return () => controller.abort();
}, [postId]);
if (error) return <div>Error: {error}</div>;
if (!post) return <div>Loading...</div>;
return <h1>{post.title}</h1>;
};You understand every line because you asked why each piece exists.
The rule I follow
AI coding assistants are force multipliers. They multiply whatever intent you put into them. If your intent is to skip thinking, you'll become a worse engineer over time. If your intent is to deepen understanding, you'll learn faster than any generation before us.
Ask questions. Challenge assumptions. Force the AI to be a Socratic partner that helps you learn, not a vending machine that helps you forget. The goal isn't to code without AI. It's to think with it.