Writing
AI Can Write the Code. But Can We Verify It?
AI has made writing code much faster. It hasn’t made understanding code any faster, and the more code we accept into our systems, the more that gap matters.
By Ka Lun Chan · Engineering leadership · AI / Engineering judgment / Leadership
Each abstraction moved the complexity
I’ve watched the tools change several times. On a carrier network I automated configuration with Expect and Korn shell scripts. I wrote VoIP apps for Nokia phones in C++, built voice platforms on Asterisk and OpenSIPS, moved a publishing platform to Rails, and moved systems out of racked servers into AWS. Each step made some work dramatically easier.
None of them made the complexity disappear. It moved somewhere else. The cloud meant I stopped racking servers, and capacity planning turned into cost management. Splitting a system into services made each piece easier to change, and turned function calls into network calls that can time out, retry and arrive twice. An ORM hides SQL right up until a page makes a few hundred queries. The work that remained was usually the part that needed someone to understand what was going on underneath.
AI-assisted development is the latest step, and the biggest one I’ve seen for the speed of turning an idea into working code. I use AI coding tools every day, and I wrote about how in recognizing AI-generated code. The complexity hasn’t gone anywhere this time either. It has moved into understanding what was generated.
Code arrives instantly, comprehension doesn’t
Werner Vogels put this well in his closing keynote at AWS re:Invent last December, which he said would be his last. Most of the talk was about what he calls the Renaissance Developer, and one passage stayed with me. “You will write less code, ’cause generation is so fast, you will review more code because understanding it takes time.” When you write code yourself, he said, comprehension comes with the act of writing it. When a machine writes it, that comprehension has to be rebuilt during review, and he called the gap “verification debt.” His summary was blunt: “AI can generate code faster than you can understand it. Code arrives instantly, but comprehension does not.”
He also said, more than once, that “the work is yours, not that of the tools.” I agree with that completely. When something goes wrong in production, “the AI wrote it” doesn’t explain anything to a customer or a regulator, and it doesn’t help the engineer who gets paged. Whoever accepts the code into the system owns it.
That leaves a practical question the keynote didn’t try to answer, and it’s the one I think about most when I review my own AI-assisted work. How much do I need to understand before I accept a change?
Five levels of verification depth
I find it useful to think about verification as depth. These levels are my own way of describing it, not a formal standard, and each one assumes the ones before it.
| Level | What it establishes | What it catches that earlier levels miss |
|---|---|---|
| 1. Does it run? | It compiles, starts and the tests pass | Syntax errors, missing imports, obvious breakage |
| 2. Does it solve the right problem? | It meets the real requirement, edge cases included, without extra complexity | A correct answer to the wrong question |
| 3. Does it fit the system? | Queries, API contracts, authorization, errors, concurrency, data and cost all hold up | A function that works alone and hurts something else |
| 4. Do we understand how it fails? | Behavior under load, with failed dependencies and odd input, and who can debug it | The incident nobody on the team can explain |
| 5. Can we trust it in production? | Independent testing, security review, observability, safe rollout and rollback, an owner | A change nobody can watch, undo or answer for |
Level 1: Does it run?
The code compiles, the application starts, the tests pass, and the feature appears to work when I click through it. This is where AI-generated code is strongest, and also where it’s most convincing. When the same tool writes the code and the tests, the tests can pass with the bug still in place, which I showed in an earlier post. Running proves very little about correctness.
Level 2: Does it solve the right problem?
Here I check the implementation against what was actually needed. Does it meet the requirement as the user would describe it? Does it handle the empty list, the duplicate submission and the record that’s halfway through being edited? And did it solve the problem with roughly the amount of code the problem deserves? Generated code tends to be thorough in places nobody asked for, and every extra branch is something to test and maintain.
Level 3: Does it fit the system?
A function can be correct on its own and still cause problems around it. This level asks how it behaves with everything else: whether the query uses an index or scans a large table, whether an API change breaks an existing client, whether authorization is checked on the object as well as the route, how errors propagate, what happens when two requests change the same record, whether the data stays consistent, and what it adds to the infrastructure bill. An AI tool only sees the context it was given, and most of a real system lives outside that context.
Level 4: Do we understand how it fails?
Every system fails eventually, so the question is whether we know how this part will fail. What happens under load, when a dependency times out, or when the input is something nobody planned for? Can someone troubleshoot it in production at two in the morning from the logs it writes? Can another engineer change it safely next year? Code I can’t explain is code I can’t debug, however clean it looks.
Level 5: Can we trust it in production?
The last level is operational. The change has been tested by something other than the tool that wrote it, security-sensitive parts have had a real review, performance has been checked against realistic data, there is enough observability to see it misbehave, the rollout can be stopped and rolled back, and someone owns it afterward. This is where the familiar practices of running production systems come back in, and none of them got easier because the code was generated.
How deep is deep enough?
How deeply developers should verify AI-generated code depends on the risk of the change. A copy change may only need to run and look right. Code that touches money, permissions, personal data or a production database needs the developer to understand how it fits the system, how it fails and how it will be operated. Every change doesn’t need every level, and I’d rather decide the depth before accepting the code than after something breaks.
A button label, a layout tweak or an internal report can stop at the first two levels. A new API endpoint usually needs the third. A financial calculation, an authentication flow, a permission rule or a database migration needs all five, whoever or whatever wrote it. I went into matching review effort to the type of change in code review in the AI era, and the same thinking applies here from the author’s side: the person accepting generated code should know which level the change needs before they call it done.
Going deeper than the risk requires has a cost too. If a copy change gets the same scrutiny as a migration, the team slows down for no benefit and people learn to treat review as ceremony. I’m not aiming for the most verification possible. I want enough confidence for what’s at stake.
Understanding is now the expensive part
For most of my career, writing code and understanding it cost roughly the same effort, because they happened together. AI split them apart. Writing a change can now take minutes, while understanding it at level three or four takes about as long as it always did. As writing gets cheaper, understanding becomes a much bigger share of what software costs.
That shift has consequences beyond the individual developer. If a team can generate more code than it can meaningfully verify, the difference doesn’t disappear. It turns into unverified behavior in production, a review queue that slows everything down, or review that quietly becomes a skim. Verification capacity, mostly the time of people who can judge whether a change is right, becomes the real limit on how fast a team can safely ship.
I think that changes a few things. When I’m hiring, I care at least as much about whether someone can read unfamiliar code, explain why it’s wrong and debug it as about how fast they can produce it, which ties into why hiring good engineers is so hard. When planning, review and testing time belong in the estimate instead of being squeezed in after the code is “done.” And keeping changes small matters more than ever, because a reviewer can hold a small change in their head and can’t do that with a two-thousand-line pull request. That’s why I keep coming back to smaller stories and pull requests.
I also look at the cost per change that’s accepted and stays correct, rather than the cost of producing it. I worked through that comparison in the hidden cost of AI coding. A change that took five minutes to generate and two days to untangle wasn’t cheap.
How will juniors learn to spot a wrong answer?
This is the part I’m least sure about. I learned to recognize a bad implementation by writing a lot of them: getting stuck, reading logs, investigating behavior that made no sense, reading other people’s code, and slowly understanding why something failed. That’s where judgment came from. If AI writes most of the first draft, the work left for a junior developer is increasingly verification, which happens to be the work that depends most on judgment they haven’t had the chance to build yet.
I don’t think AI automatically makes developers less capable, and I don’t think anyone needs to write everything by hand to learn. Used well, an assistant is a patient tutor that will explain a concept five different ways. The early research I covered in AI and the learning curve points the same way: people who used AI to explain and question code scored much higher than people who handed the whole task over. How someone uses the tool matters more than whether they have it.
What changes is that the learning has to be designed on purpose. In practice that means asking juniors to explain generated code in review, in their own words, and to say what they checked. It means some debugging without the assistant, reading code they didn’t write, and owning small changes from start to finish, including what happens after deployment. Vogels made a similar point about code review: more of it person to person, with experienced and newer engineers working through code together. “AI will change many things,” he said, “but the craft is still learned person to person.” That matches why I enjoy mentoring, and why I think it matters more now.
Broad enough to ask the right questions
Vogels’ Renaissance Developer has five qualities, in his own recap: be curious and keep learning, think in systems, communicate with precision, be an owner, and become a polymath. By polymath he means T-shaped, deep in one area with a working understanding of others. I think breadth is the quality that matters most for verification, because most of the problems at levels three and four sit at the boundary between specialties.
Take a developer who generates a database-backed API endpoint. They don’t need to be a database administrator, a security engineer and an SRE. They do need to know enough to ask whether the query will use an index as the table grows, whether a user can read another user’s record by changing an ID, whether the list endpoint is paginated, what happens when the database is slow, and what the endpoint will cost to run. Just as important, they need to recognize when the answer is beyond them and bring in someone who knows more. I wrote about that range in the generalist advantage in the AI era.
That’s also why I don’t expect any one engineer to cover every specialty. Good teams don’t expect everyone to know everything. They make it normal to ask, so the person who knows the database or the security model gets pulled in before the change ships, and everyone else learns a little in the process.
What changes for engineering leaders
When implementation gets faster, the familiar measures of productivity get less meaningful. More pull requests, more lines or more tickets closed can simply mean more code that someone has to understand and maintain. More generated code doesn’t automatically mean more business value. The questions I find more useful are these:
- Are we solving the right problems, or building faster in the wrong direction?
- Do our code reviews verify anything, or have they become approvals?
- Can the author of a change explain how it works and how it fails?
- Is AI adding maintenance cost we aren’t counting yet?
- Are junior engineers building judgment, or only producing output?
- Can the team operate and debug what it ships?
I push back on unnecessary complexity a lot, and I wrote about why in what engineering leadership actually does. AI raises the stakes. It costs almost nothing to generate an elaborate implementation with extra configuration, abstraction layers and options nobody asked for, and every bit of it still has to be understood, tested and maintained by people. A simpler solution is easier to verify at every level in the table above. Being able to generate more software doesn’t mean we should build more software.
I’m still adjusting too
I don’t have this worked out. I’m still finding where the depth should sit for different kinds of work, and the right answer isn’t always obvious until after a change has been running for a while. I expect it to keep shifting as the tools improve and as I learn which of my checks actually catch problems.
These are my views from my own work and the teams I’ve led. Other teams, with different systems and different risks, may reasonably land somewhere else.
Sources
Quotes were checked against the keynote video in October. The levels, examples and opinions are mine.
Short answers
How deeply should developers verify AI-generated code?
As deeply as the risk of the change requires. A copy change may only need to run and look right. Code that touches money, permissions, personal data or a production database needs the developer to understand how it fits the system, how it fails and how it will be operated. Every change doesn’t need every level, and the depth should be decided before the code is accepted.
What is verification debt?
Werner Vogels used the term in his closing keynote at AWS re:Invent. When you write code yourself, understanding comes with writing it. When a machine writes it, that understanding has to be rebuilt during review. Code that ships before anyone rebuilds it carries verification debt.
How do junior developers learn to judge AI-generated code?
The same way judgment has always been built, with more deliberate design: explaining generated code in review, debugging without the assistant sometimes, reading unfamiliar implementations, and working through changes with someone more experienced. Using AI to explain and question code helps learning. Handing over the whole task skips it.
Why does AI make understanding code the expensive part?
Writing and understanding code used to happen together, so they cost about the same effort. AI made writing much cheaper and left understanding about where it was. The time of people who can judge whether a change is right becomes the limit on how fast a team can safely ship.
What happens after the code is generated
AI has changed how quickly we can turn an idea into working software, and I’m genuinely excited about that. What interests me more and more is what comes after: whether we can explain the code, test it, maintain it, recognize when it’s wrong, and trust it when something unexpected happens. The tools will keep changing, as they always have.
I don’t think the future belongs to the developers who generate the most code. I think it belongs to the ones who understand what they’re building and know how to verify it.