
The hardest part of using AI is
being able to understand code by reading it is different from being able to do that same modeling yourself starting from a blank screen
and it becomes difficult to tell the two apart.
I genuinely believe that reading code frequently leads to improvement. Read lots of good code. Absorb lots of good patterns. Do that long enough, and you start storing them like pattern matches.
When you program for any length of time, you naturally come to recognize good code from bad. Most developers follow at least one programming influencer, or read posts by programmers like me, and those people tend to have preferred projects. Follow those projects long enough and you begin to feel that code gravitates toward certain shapes.
So as you wander the vast open terrain of open-source code and public GitHub repositories alongside the private land of your own company's codebase, you encounter code that feels right to you, code that seems to fit. And you tend to settle into that Paradigm.
Programming, then, tends to pull you toward working within a single Paradigm. That Paradigm is usually tied to a specific domain, and it becomes your trade.
In that sense, programming generally unfolds like an Apprenticeship system. Developers tend to carry with them the style of their first company or the first open-source community they engaged with, and they write code within that Conceptual framework. Some people who call themselves self-taught do build their own distinctive style, but it usually grows out of whatever gave them their first taste of success.
That is why a challenge to a programming paradigm can feel like a denial of one's entire worldview.
At their first job, most people learn things like these:
"The
servicelayer owns this responsibility."
"This is how you handle exceptions."
"This is how DB transactions must be structured."
Repeat that for a few years and you develop an unconscious sense that code is simply supposed to look this way. That is what is called Tacit knowledge.
Because a critique touches the compression scheme someone has spent years using to solve problems, it is easy for things to get emotional.
For example, if you tell someone who has been doing Object-oriented programming (OOP) for ten years,
"The abstraction of objects itself is flawed,"
that does not land as a simple criticism of an API choice. Instead, it sounds like,
"The way you have been seeing problems for the past ten years is wrong."
And that stings.
Most people who graduate from the ACM Curriculum or a university computer science program come away knowing that the building blocks of software exist, but where and how to apply them is entirely a matter of experience.

In culinary terms, university teaches you knife skills, and then you have to figure out which discipline you want to pursue.
Imagine someone who wants to become a chef.
After learning knife skills, ingredient properties, food safety, and basic cooking techniques in school, they now need to find a job. In programming terms, they have learned the basics: algorithms, data structures, operating systems, and networking.
There is French cuisine, which prizes refined sauces and meticulous technique. There is Japanese cuisine, which prizes the natural flavor of ingredients and seasonality. There is Chinese cuisine, which handles an enormous range of ingredients in an enormous range of ways. There is Korean cuisine, where the time spent on fermentation and preservation is central. There is American cuisine, shaped by a mix of many cultures, and Indian cuisine, where the combination of spices is everything.
Whichever you choose, knife skills remain highly relevant as a foundation. (I say "highly relevant" rather than "essential" because most things are well abstracted these days.) Even so, becoming a great French chef and becoming a great Japanese chef are not the same journey.
Even when using the same knife, what you cut, how thinly you cut it, when you put it over heat, what you discard and what you keep from each ingredient all differ. In some cuisines, fermentation matters more than knife work; in others, fire control is the key skill; in still others, judging the ripeness or aging of an ingredient is what matters most.
The craft of programming is similar.
School teaches sorting algorithms and data structures, but the moment you join a real company you suddenly have to learn things like service layers, repository patterns, transaction boundaries, API contracts, retry logic, logging, and deployment. Web developers learn to think in terms of HTTP and databases; game developers think first about frames, state, and memory layout; embedded developers think first about timing and hardware boundaries.
None of this conflicts with the fundamentals learned in school; a specific kitchen's Conceptual framework is simply built on top of that foundation.
So what people commonly call "real-world coding" is not simply a matter of learning code hygiene that school never covered. It is closer to a process of learning which problems to claim as your own, what to treat as important, and what to sense as dangerous.
A French chef can look at a sauce and judge whether a dish is finished. A Japanese chef can read a great deal from the condition of a fish or the angle of a cut alone. A Korean chef stands before fermented ingredients and reads the time of their aging: the sourness of kimchi, the deep aroma of a fermented paste, the salinity of salted seafood, the temperature inside the jar and the season outside, all layered together to judge where in the process things stand.
So they are not looking at the ingredients as they are now; they are reading what those ingredients have been through and where they are headed.
In the same way, someone who has spent years in web backend work is sensitive to transactions and data consistency, while a game programmer notices allocation patterns and frame spikes first.
Professional experience, in the end, is not just an accumulation of techniques for solving problems. It is a process of learning what to recognize as a problem in the first place.
AI, however, sits as a strange presence somewhere between cooking school and the Apprenticeship system.
AI can produce French cuisine, Japanese cuisine, and Chinese cuisine. Ask it for a French approach and it delivers one; ask it to switch to a Japanese style the next moment and it does that convincingly too. It is even better at it than I am.
So I can now encounter far more varieties of cooking (code) than I ever could before.
The problem is that being able to taste a dish and evaluate it is different from being able to open a refrigerator with nothing decided and design the same dish from scratch.
Looking at an already finished plate and thinking, "the plating of the sauce is off," or "the salt level is so high that people who like Japanese food would enjoy it, but it does not suit Korean cooking," or "this is too spicy and wrong for someone who prefers Japanese cuisine" β that kind of Conceptual framework does not form when you are simply consuming finished results.
When I give AI a planning document and a spec, it outperforms me on the detail work by a wide margin. (This is partly a limitation I have as a non-native English speaker.) It writes more readable function names, uses better-performing algorithms, and where I would compromise on performance because I cannot implement something, it draws a clear boundary and delivers better performance too.
Ask for Object-oriented programming (OOP) today and it divides things into objects and responsibilities; ask for Functional programming tomorrow and it restructures everything as immutable data and transformation flows. Request Domain-Driven Design (DDD) and it produces aggregates and domain events; say you just need simple Create, Read, Update, Delete (CRUD) and it drops that complexity and keeps things straightforward.
I tend to write defensive code to excess, but AI writes either even more defensively or with real conciseness.
In my third year I completed a 50,000-line program entirely on my own, from scratch. Building on that Conceptual framework, I shifted from simple maintenance and legacy code fixes to delivering complete software projects. But these days, AI can produce code like that in less than a day.
When I was learning from a senior developer, I was at least exposed to that one person's Conceptual framework for a long time. AI, by contrast, can imitate countless Conceptual frameworks simultaneously.
So the more you use AI, the more varieties of code you encounter. That in itself is a tremendous advantage. Code styles that would once have taken years and multiple companies to see can now be encountered within months.
But as that continues, I fall into a significant illusion: it becomes hard to tell whether I have actually learned a Paradigm or whether I have simply seen a lot of output written from within that Paradigm.
Consider this example: suppose AI models failures using Result<T> at the center and divides states with a Sum type.
Say it produces code that handles branching through exhaustive pattern matching. I can read that code and explain why it is good. If a branch is missing, I can spot it. This is simply a skill that improves with enough reading.
type Result<T, E> =
| { kind: "ok"; value: T }
| { kind: "err"; error: E };
type PaymentState =
| { kind: "pending"; orderId: string }
| { kind: "authorized"; orderId: string; paymentId: string }
| { kind: "captured"; orderId: string; paymentId: string }
| { kind: "failed"; orderId: string; reason: string };
type PaymentError =
| { kind: "invalidAmount" }
| { kind: "gatewayTimeout" }
| { kind: "cardDeclined" };
function capturePayment(
state: PaymentState
): Result<PaymentState, PaymentError> {
switch (state.kind) {
case "pending":
return {
kind: "err",
error: { kind: "invalidAmount" }
};
case "authorized":
return {
kind: "ok",
value: {
kind: "captured",
orderId: state.orderId,
paymentId: state.paymentId
}
};
case "captured":
return {
kind: "ok",
value: state
};
case "failed":
return {
kind: "err",
error: { kind: "cardDeclined" }
};
default:
return assertNever(state);
}
}
function assertNever(value: never): never {
throw new Error(`Unhandled state: ${JSON.stringify(value)}`);
}But if the code is deleted entirely and I am told to do the Modeling again from scratch, can I? That is a genuinely difficult question.
Deciding from the beginning which states belong to a single type, which failures deserve their own separate case, what counts as an invariant, and which object should own a given state β that is an entirely different problem.
This is where I think Recognition and Generation are distinct.
The ability to look at an existing structure and judge whether it is good or bad, and the ability to generate possible structures from nothing and choose among them, are related but not the same thing.


A great critic and a great creator have different kinds of skill. An outstanding film critic can precisely analyze the composition of a shot, its editing, and the weaknesses of a narrative, yet that does not necessarily mean they can make a great film. Conversely, a great director or novelist may not be able to articulate in theoretical terms why they made the choices they did.
There is a famous anecdote about Hitchcock's granddaughter submitting a film report based on an interview with Hitchcock and receiving a C, prompting people to say, "How dare the teacher interpret the author's intent when they don't even know who he is."
But think about it: just because Hitchcock could make his films does not guarantee he was the person best positioned to put into words the social impact those films had and how they persuaded audiences. The creator makes choices; the critic analyzes the structure those choices produce. A critic may even discover patterns or repetitions the creator never consciously intended, while on the other hand an interpretation a critic constructs may have nothing at all to do with what the actual filmmaker intended.
The point is this: making something and recognizing something are two different kinds of mastery.
Programming is similar.
Some people look at code and quickly spot where responsibility is misplaced, where state is leaking, where an abstraction is unnecessary, or where a boundary will cause trouble down the road. Yet they may still struggle to produce a program of the same quality from scratch on a blank screen.
Others are remarkably good at building new systems. Given just a few lines of requirements, they identify the right data model and boundaries and bring the program to completion. Even so, they may find it awkward to review someone else's code or to explain in words why they chose the structure they did.
Seen that way, using AI feels like exercising a critic's ability. The problem is that this bleeds into the creative side and neatly conceals my own incompetence.
The moment I look at a flawless Result pattern and union type generated by AI and click the Approve button thinking, "Nice, the state separation is very clean," I start to believe I designed that architecture myself. I gave the prompt, so I think of myself as the creator, but in reality I have stepped back into the role of a food critic at a buffet AI laid out, tasting and evaluating finished dishes.
A sharper critical eye does not train the creative muscle. If anything, endlessly consuming the overwhelming quality of answers AI produces means the painful Modeling sense β the one that had you clumsily declaring variables and drawing boundaries on a blank screen β atrophies quickly.
That said, staring at a blank screen and generating designs from pure intention means giving up both market viability and the overwhelming implementation power that comes from using AI. In that sense, the ability to leverage AI might be comparable to a programmer being promoted into management.
A manager no longer needs the ability to write enormous volumes of code. Instead, they decide who should do what, judge whether results meet requirements, and stop things from heading in the wrong direction midway. The common observation that a good manager and a good individual contributor are different people comes from exactly this. The weight shifts from the ability to produce directly to the ability to evaluate output and set direction.
Programmers who use AI are gradually moving into a similar position.
Instead of writing every function yourself, you break down the problem, communicate the constraints, review what AI produces, and ask it to rewrite anything you are not satisfied with. You make tests pass, find the places where the output conflicts with existing code, and ultimately decide which implementation to accept.
This is clearly a necessary skill. And when it comes to productivity β that is, shipping a feature quickly β it is overwhelmingly stronger than writing all the code yourself.
Just as one manager can direct multiple workers, one programmer can drive multiple agents simultaneously. So in the AI era, productivity is increasingly limited by the speed of judgment rather than the speed of writing.
But there is a longstanding problem with management roles.
A manager who steps away from the front line may, over time, lose the ability to perform the work they are managing.
The same thing happens in development.
At first, you hand off to AI code you could write yourself. A little later, you hand off code that would be tedious to write yourself. Later still, you hand off code that would be difficult to write yourself. And at some point, AI is producing code you could not write from scratch at all, and you are simply reading and approving it.
What is strange is that this process feels entirely natural. There is no single moment when you suddenly lose the ability.
The move between each stage is tiny, so it is hard to notice when you have crossed a threshold. Once you cross that line, the ability is gone. Most of the cases I have seen where senior programmer-managers can no longer understand newer code and quietly delegate reviews to the person just below them β nearly all of them trace back to exactly this.
The market now asks me to write code with AI. And writing code with AI is genuinely fun. Of course, there will be people who dislike it.
But,
does being able to evaluate code mean the same thing as owning the mental model of that program?
That thought always feels lodged in my throat like a thorn. So I write it down here. I am setting my programming mental model down outside myself, so that I can read it again someday and recover it.