AI Isn’t Coding for You. It’s Generating More Code for You to Review
There’s a comfortable lie circling software engineering right now: now that AI writes code, programmers will finally spend less time on tedious work and more time thinking about what actually matters.
Nice story. Almost poetic. Also a little dangerous.
Because in practice, a lot of people aren’t working less. They’re working differently. You used to spend energy figuring out how to write an implementation. Now you spend energy figuring out whether the implementation that magically appeared in your editor actually holds up, whether it respects the system’s context, whether it opened a security hole, and whether it invented an abstraction that looks elegant today but turns into a small emotional bomb three months from now.
AI reduced the friction of generating code. That’s real. But it increased something else: the volume of plausible-looking things that now need to be evaluated.
And that’s the part the hype conveniently ignores. Producing code was never the whole bottleneck. The bottleneck was always producing code that’s correct, sustainable, and fit for the real system. Everything else is autocomplete with an ego.
Where Did the Productivity Actually Go?
When someone says “AI made me more productive,” I believe them. I just always want to ask: productive in which part, exactly?
Did you write less boilerplate? Great. Build tests faster? Even better. Understand an unfamiliar library in less time? Perfect. But did you spend more time reviewing AI output? Did you have to redo a solution that looked right but ignored a business rule? Did you accept polished code that later broke on an edge case? That’s where the conversation gets interesting.
Productivity in software isn’t a straight line. It’s full of hidden trade-offs. Sometimes you save 20 minutes writing a function and spend 40 trying to understand why the AI chose that particular path. Sometimes it hands you a solution that’s 80% done, but the remaining 20% needs so much context that writing it from scratch would have been simpler. And sometimes, sure, it nails it completely. I’m not here to pretend AI doesn’t help. It helps a lot.
The problem is selling it as pure net gain.
A recent study on coding assistants called this shift supervisory engineering work: engineering built around supervising, evaluating, and correcting output produced by AI. It’s a useful label because it names something a lot of people already feel day to day. The developer hasn’t left the workflow. They’ve become a kind of editor-in-chief for an extremely fast colleague who writes with total confidence and doesn’t always understand what they’re doing.
The perfect intern and the dangerous intern, at the same time.
AI-Generated Code Has One Problem: It Looks Right
The worst code in the world isn’t the code that’s obviously bad. That kind at least scares you on sight. Terrible variable names, absurd duplication, a 200-line function, a comment reading “temporary fix” written four years ago. You look at it and already know there’s a body buried somewhere.
AI-generated code tends to be more deceptive. It arrives formatted. It uses reasonable names. It follows familiar patterns. It might even include tests. Sometimes it explains its own solution with an almost offensive level of calm. The problem is that “looks right” isn’t the same thing as “is right.”
Imagine asking the AI to write a simple authorization function:
function canAccessProject(user: User, project: Project): boolean {
return user.organizationId === project.organizationId;
}Beautiful. Minimal. Direct. It would sail through most code reviews with the classic comment: “LGTM.”
Then the real world shows up, that inconvenient thing. Is there a suspended user? A project shared across organizations? A global admin? A temporary permission? An expired contract? An audit requirement? A feature flag? That one enterprise client with a special exception because they signed a contract in 2021 and now everyone’s afraid to touch it?
The AI didn’t get the TypeScript wrong. It got the domain wrong.
And software doesn’t only break from syntax errors. It breaks because someone implemented an oversimplified version of reality.
Reviewing AI Code Requires More Context, Not Less
There’s a fantasy that if the AI writes the code, the developer can just skim through the review quickly. But reviewing code you didn’t write, code that maybe nobody actually “thought through,” can demand even more attention.
When a person opens a PR, you can ask, “why did you choose this approach?” There’s an intention behind it. Even if the intention is wrong, it exists. With AI, what you often have is a statistically plausible answer to an incomplete request. It’s not defending an architecture. It’s completing a pattern.
That changes the kind of review you need to do.
You’re not just reviewing style, performance, or readability. You’re reviewing assumptions. What is this code assuming about the user? About the database? About the API? About concurrency? About security? About partial failure? About cost? About what happens when this job runs twice because, surprise, distributed infrastructure didn’t read your sprint plan?
This is an important detail: AI can accelerate the creation of a candidate solution. But a candidate still needs to be validated. And depending on the system, validation is the expensive part.
In a small side project, that might sound like overkill. In a real product, with real users and real money involved, it isn’t. Wrong code on some internal screen might just be annoying. Wrong code in billing, permissions, authentication, sensitive data, or financial processing turns into a serious-faced meeting and a postmortem with a passive-aggressive title.
The Risk Is Confusing Speed With Delivery
Generating code fast feels amazing. It’s almost addictive. You ask for a function, it appears. Ask for a test, it appears. Ask for a refactor, it appears. Ask for documentation, it appears. The editor turns into a dopamine machine for a tired engineer.
But delivery isn’t generation. Delivery is code that works in production, that can actually be maintained, and that doesn’t scare the next developer who opens that file.
Get Luiz Picorelli’s stories in your inbox
Join Medium for free to get updates from this writer.
This is where a lot of teams are going to get tangled up. Surface-level metrics will improve: more PRs, more commits, more lines, more tasks “in progress.” But the system underneath can get worse. More disguised duplication. More abstractions that look similar but aren’t identical. More local decisions that ignore the overall architecture. More “good enough” code getting merged because reviewing everything in depth has become unrealistic.
AI increases throughput. But if review, testing, and architecture practices don’t keep up, you’re just filling the pipeline with dubious code faster.
It’s like installing an industrial faucet over a clogged sink. It feels like productivity right up until the kitchen starts flooding.
Tests Become Even More Important
For years, a lot of people treated testing as that important thing everyone defends in public and neglects the moment a deadline gets tight. With AI in the workflow, that habit becomes more dangerous.
If you’re going to accept more code you didn’t write by hand, you need a better safety net. Unit tests, integration tests, contract tests, property-based tests where it makes sense, CI validations, linters, static analysis. None of it is glamorous. None of it goes viral. But it’s what stops a confident assistant from turning your codebase into a museum of bad decisions.
Using AI well for testing isn’t just asking it to “write tests for this function.” That helps, but it can also generate tests that merely confirm the current implementation, including when that implementation is wrong. The better use is asking for edge cases, negative scenarios, malformed inputs, concurrent situations, examples that break the original assumption.
For example:
describe("canAccessProject", () => {
it("denies access when the user is suspended", () => {
const user = {
organizationId: "org_1",
status: "suspended",
role: "member",
};
const project = {
organizationId: "org_1",
visibility: "private",
};
expect(canAccessProject(user, project)).toBe(false);
});
});This test looks simple, but it carries a domain question: can a suspended user access something? The AI might not know. The team needs to.
That’s where engineering actually shows up. Not in the ability to generate the function. In the ability to decide which behavior matters.
The Good Developer Becomes Editor, Architect, and Investigator
There’s an identity shift happening. Programming with AI feels less like “requesting code” and more like editing the work of someone who delivers drafts very fast.
And editing is hard.
A good editor doesn’t just fix commas. They understand intent, cut what’s excessive, notice inconsistency, spot gaps in logic, and know when to throw the whole paragraph out. A good engineer using AI does something similar. They don’t accept the first answer just because it compiled. They ask: is this solution simple enough? Is it in the right place? Does it repeat logic that already exists? Does it break some contract? Does it introduce unnecessary coupling? Is it actually solving the problem, or just satisfying the prompt?
That kind of judgment is what will separate good developers from merely fast ones.
In fact, AI might make fundamentals more important, not less. Knowing how to model a domain matters more. Knowing how to read code matters more. Knowing how to test matters more. Knowing how to ask the right questions matters more. Knowing how to say “we’re not implementing it this way” matters a lot more.
The developer who measures their own value purely by volume of code produced is going to struggle. Because volume is cheap now. What’s expensive is judgment.
The Problem Isn’t Using AI. It’s Abdicating Responsibility
I don’t buy the anti-AI argument that all AI-generated code is bad, impure, or a sign of a lazy developer. That’s nostalgia dressed up as technical principle. Nobody misses hand-writing repetitive boilerplate, and nobody should feel proud of spending an afternoon on something a tool solves in two minutes.
But I also don’t buy the opposite argument, that using AI is enough on its own, productivity unlocked. That take ignores a basic fact: the code still enters a system that someone is accountable for.
When a bug reaches production, there’s no field in the postmortem that says “the model’s fault.” When a data leak happens, legal doesn’t care whether the function came from Copilot, ChatGPT, Cursor, or a developer listening to lo-fi at two in the morning. The responsibility still belongs to the team.
AI can be an informal co-author. But the person hitting merge is human.
And that detail changes everything.
Maybe the New Productivity Is Knowing How to Reject More
In the end, I think the most underrated skill in AI-assisted programming is going to be rejecting code.
Not rejecting out of stubbornness. Not rejecting because “I’d have done it differently.” Rejecting because the solution doesn’t understand the domain, doesn’t respect the architecture, invents unnecessary complexity, hides risk, or simply solves the wrong problem with a lot of elegance.
AI makes creating easy. So the value shifts to choosing.
Choosing what gets merged. What gets sent back. What needs a test. What needs a conversation. What needs documentation. What needs to be deleted before someone falls in love with an unnecessary abstraction.
The original promise was that AI would free the programmer to think more. Maybe that’s still true. But only if we don’t end up using AI as an excuse to stop thinking.
Because in the end, the question isn’t whether AI writes code.
It does.
The question is: who’s reading it carefully before it goes into production?





