
The hardest question about a piece of software has never been “Who typed this?” It has been “Why should we believe it is correct?” That question does not get easier when an AI wrote half the file. If anything, it gets harder, because now you are being asked to audit work you did not produce, in a format you did not have to construct, with assumptions you may not share.
Code generation is suddenly cheap, and that is the whole premise. Tools that complete a function from a comment, write a test suite from a description, or refactor a module from a sentence are no longer science fiction. The cost of producing a plausible implementation is dropping faster than the cost of checking whether it is right. That asymmetry is where the interesting problems live. Deloitte’s 2026 software industry outlook describes a movement toward AI-assisted workflows across the whole development lifecycle, not just code completion[1], which makes this a practical question right now.
Writing Code and Understanding Code Are Different Activities
Here is the cleanest way to see the split. You can write a correct implementation of binary search without being able to explain why the middle line is written as low + (high - low) // 2 instead of (low + high) // 2. The first version does not overflow on a billion-element array; the second one can, and it sat in Java’s standard library for nine years before anyone noticed.
I wrote a whole post about this, Binary Search, Explained[2], and the honest takeaway is that correctness is a property of the reasoning, not the fingers. The algorithm can be right while the port is wrong, and vice versa. AI generation collapses the distance between “have the idea” and “have the file,” but it leaves the distance between “have the file” and “have an argument for why it works” completely intact.
The Danger of “It Works”
The tempting reply is that generated code does not need deep understanding if it passes the tests. Tests are a filter, not a proof. They exercise the cases you thought to check, on the inputs you thought to provide, in the order you thought to run them.
A convincing demo says very little about authorization, failure recovery, concurrency, or long-term maintenance. Those properties are rarely the ones the tests target, because they are properties of the system, not of the function. “It works” is a statement about observed behavior in a narrow window. “It is correct” is a statement about behavior across the window you have not looked at yet.
This is the same trap that shows up in my post, The VIA Timer That Wouldn’t Fire[3]. The emulator behaved correctly on the observable cases and was wrong about the internal mechanism. The bug only surfaced when the operating system depended on the part nobody tested.
Fundamentals Are Tools for Verification
This is where the old material earns its keep. Fundamentals are not a nostalgic defense of hand-written code; they are the means by which delegation becomes responsible engineering. They give you a vocabulary for asking “what does this assume?” and a method for checking the answer:
Invariants. A binary search maintains the claim that the target, if it exists, lies somewhere between
lowandhigh. Hold that invariant and the discard lines almost write themselves. Generated code either preserves the invariant or it does not, and only the invariant lets you tell.Types. A type is a claim about every value a piece of code will ever see. When a generated function deserializes JSON into a struct, the type is the place where the claim lives. If the claim is wrong, the bug is not in the function; it is in the model.
State transitions. Systems fail when a state is reachable that the designer did not imagine. Enumerating the states, or at least the classes of states, is what makes the failure modes visible before they ship.
Boundaries. Every interface is a contract. What does an empty result mean: no data, no permission, or an error? An API that leaves that ambiguous is relying on a helpful tone of voice, and machines are terrible at tone.
None of this requires you to have written the code. It requires you to understand the system well enough to state what the code is supposed to do.
A Concrete Example: The Boundary Conditions
Suppose you ask a model for a binary search. It returns something like this:
def binary_search(items, target):
low, high = 0, len(items) - 1
while low <= high:
mid = (low + high) // 2
if items[mid] == target:
return mid
elif items[mid] < target:
low = mid + 1
else:
high = mid - 1
return -1
It looks fine. It will pass most tests. It is also wrong on arrays large enough to overflow low + high, and it silently misbehaves on an empty list, where len(items) - 1 is -1 and the loop body never runs.
The fix is the invariant, stated out loud: the target, if present, is between low and high inclusive, and the middle index must never overflow. Rewrite the middle line as mid = low + (high - low) // 2 and the overflow vanishes, because high - low is never negative and low plus that difference never exceeds high.
The point is not that the model got it wrong. The point is that you can tell, from the invariant, before you run a single test. That is the skill that transfers. You do not need to have written the function to know what it must satisfy.
A Standard for Accepting Generated Code
If you will not personally write it, you still need a personal standard for accepting it. Four questions, asked in order:
Can you explain its assumptions? Every piece of code rests on claims about its inputs, its environment, and its callers. If you cannot state them, you cannot evaluate the code, because you do not know what “correct” would mean here.
Can you identify its failure modes? What breaks, and how? What happens on empty input, on adversarial input, when the network is down, when the cache is cold? A function with no identifiable failure modes is a function you have not examined.
Can you test the important properties? Not the happy path. The boundary conditions, the invariants, the error paths. If the answer is “the tests look fine,” that is not the same as “I tested the property that matters.”
Can you change it without asking the model to reconstruct your understanding? This is the real test. If a follow-up prompt cannot modify the code because the reasoning lives only in the model’s head, then you have accepted a black box with a nice interface. If you can read it, change it, and extend it, then understanding has actually landed somewhere.
The fourth question is the sharpest. It separates code that was generated from code that was understood.
Conclusion
The future of software engineering is not a story about humans versus machines. It is a story about what part of the work survives the shift from “write it yourself” to “describe it and verify it.” Code production is becoming a commodity. Understanding is not.
I have been writing about this from a few angles. The fundamentals tutorials, like the one on binary search, exist because the interesting part of the algorithm is the invariant, and the invariant is what you hold when everything else can be generated. My post, AI as an Assistant, Not a Replacement[4], argued that the realistic outcome is collaboration, not replacement, and collaboration only works when the human can audit the machine. The Model Context Protocol, Explained[5], is, at bottom, an argument that the boring, shared infrastructure is what makes the whole ecosystem trustworthy.
The common thread is that delegation is not abdication. When you hand off implementation, you hand off the easy part. You keep the part that asks: what does this mean, what does it assume, and how do we know the pieces fit together? That question has always been the hard one, and the habits behind it are what I described in Linguistics, Theology, and the Shape of Good Software[6]. The tools are just making it visible.
Deloitte, “2026 Technology Industry Outlook: Software” (2026). The report describes AI-assisted workflows spreading across the development lifecycle, not just code completion. ↩︎
Binary Search, Explained. My walkthrough of the algorithm, its invariant, and the overflow bug that lived in Java’s standard library for nine years. ↩︎
The VIA Timer That Wouldn’t Fire. A demo that behaved correctly on the cases anyone tested while being wrong about its internal mechanism. ↩︎
AI as an Assistant, Not a Replacement. The argument that realistic AI use is collaboration, and collaboration only works when the human can audit the machine. ↩︎
The Model Context Protocol, Explained. Shared, boring infrastructure is what makes an ecosystem built on top of it trustworthy. ↩︎
Linguistics, Theology, and the Shape of Good Software. The habits of working carefully with meaning, assumptions, and complex systems. ↩︎
