A few days ago, I wrote "Understanding Over Origin" and it got alot of engagement and I'm really happy that it did because it means people took the time to read, understand my perspective and I get to engage with every one of them in the comments as well. Of course, not everyone agrees but that's irrevelant, the point is a healthy discussion between various perspective from different developers around the world.
But what I haven't told anyone is this: the more I engage, the more I realized something that I still do. I still write a lot of my code by hand and those are the parts I'm more proud of "
Not gatekeeping, not me being nostalgic or identity-defensive ( well, a little nostalgic ), that's me admitting the article I published was incomplete.
Some conversations don't end when the replies stop. My discussion with @darkwiiplayer was probably my favorite of all because more we talked, the more I found myself wrestling with an incomplete idea that refused to stay unfinished and this essay is the result.
What the Discussion Clarified
First, I need to thank the people who actually engaged with the core argument.
@unitbuilds gave his take that seams everything together and concludes the core idea of modern software enigeering: " If it can run a standardized benchmarking suite against competition, it's provable and reproducible. If that's good enough for a scientific discovery, it's good enough for programming.", they're not defending AI, they're saying with better standards available, we shouldn't choose to ignore them. He also pointed out something I'd missed, the burden of correctness has always been on the developer who signs off on the PR.
@madsendev who wrote the original article that sparked this whole discussion, showed genuine class. He came back, said the argument had "gone further," and proposed an idea that's stuck with me: what if AI-assisted projects included a standard where you quiz yourself on your own repository? Not to prove you didn't use AI, but to prove you actually understand what you built.
@komo hit on something important and i think it's my favorite takeaway from the discussion: "Maintenance receipts are much harder to fake." That single sentence reframes the entire debate. Tests. Bug fixes. Production incidents. Responding to issues. Refactoring. Every one of those leaves a trail of evidence that someone not only built the project but continues to understand, improve, and take responsibility for it. That's the kind of ownership that matters. It's earned over time, and it's far harder to fake than explaining code in an interview or claiming you wrote every line by hand.
I'm absolutely grateful that people gave in their thoughts but you can never have a good discussion without a pushback ⚔️
the pushback's always my favorite part of any technical discussion.
The Challenge That Made Me Think
@darkwiiplayer showed up and said something like: "Training AI on stolen code without consent is theft. Everything built with AI carries that theft in its DNA. You're overlooking this."
She wasn't being rhetorical, she was actually making a philosophical point about training data, consent, and what "learning" means when it's done at scale without permission.
We went back and forth. I argued that training data and generated output aren't automatically equivalent to copying and she countered that if you removed all stolen code from training data, the models wouldn't exist as they do. Which is... probably true? And that matters.
I don't think it invalidates the engineering argument. But it does mean the conversation has two layers:
- The technical layer: is the work maintained, understood, and good?
- The ethical layer: how should we feel about tools built on potentially non-consensual training data?
Most people in that thread were debating layer 1 and she dragged me down to layer 2 into the conversation. Both are real. Neither invalidates the other.
But it also made me realize my original article didn't address why handwritten code felt different to me because honestly? it does.
The Real Tension
The argument I made: origin doesn't determine quality. Understanding, testing, maintainability, accountability, that's what matters.
My own experience reflects that. I started writing code around 2018, and back then the only AI most of us could name was Sophia, the humanoid robot from Hong Kong. I learned the traditional way: by writing everything myself. Even today, when I write code by hand, I understand it differently. More deeply. I notice edge cases I might have overlooked if I'd generated the first draft with AI. And when I'm done, I'm genuinely more proud of what I've built.
These aren't compatible statements if you think about them too hard.
Either:
- My pride is just ego (origin bias)
- Handwritten code actually is better (proving the gatekeepers right)
- Both are true, but for reasons I didn't examine
@fromzerotoship helped me see option 3 clearly. They're not a developer but they've shipped 20+ working internal tools using AI. A hospital runs them. By my standards, they'd "fail" the understanding test, there are parts of their system they couldn't explain line-by-line.
But then they said something that shifted everything: "Demonstrable behavior under deliberate failure is another form of earning trust, and it's the only one available to me. It's also harder to fake."
Plant defects, watch the guards catch them, restore the guards and watch them go green and then deploy and check the URLs a few seconds later.
they demonstrate that they can keep it alive.
And that made me realize the real distinction wasn't about who typed the code. It was about friction.
The Friction Actually Matters
In my earlier piece I wrote a few weeks ago, I described not being able to write some logic from scratch despite reviewing it perfectly, that wasn't just skill loss. It was proof that I'd skipped the friction that builds actual understanding.
When I write code by hand, I encounter problems in real-time.
I hit a wall. My approach doesn't work. I refactor. I discover the problem space through constraint and failure. I make decisions about decisions, not just typing, but choosing between paths I've actually explored. this is the learning path that I unknowingly go through whenever I write code by hand and Wii reminded me of this.
When I use AI, I describe what I want. I get options. I pick the one that looks right. I'm curating, not exploring.
Both can produce good code. But the path to understanding is fundamentally different.
With handwritten code: friction, insight, better next decisions
With AI-assisted: selection, implementation, validation
The friction is the learning mechanism.
So when I say I'm more proud of handwritten code? I'm not being romantic about suffering. I'm noting that the code I'm proudest of is the code I fought for, and handwriting forces the fight.
But Here's the Problem With That Logic
If friction is the mechanism, then the type of friction matters more than who did the typing.
You could write AI-assisted code where you:
- Fight with the prompt
- Critique every generated option
- Refactor aggressively
- Hit walls and redesign
That has friction and it also builds understanding.
Conversely, you could hand-write code where you:
- Autopilot through a familiar pattern
- Never question assumptions
- Copy-paste from Stack Overflow
- Never understand why it works
That has no friction and it builds nothing.
So the honest version of my position isn't "handwritten code is better." It's: "friction builds understanding, and handwriting tends to create friction because you're forced to think through every line."
But that's a contingent truth, not an absolute one.
The Uncomfortable Realization
The critics ( gatekeepers, but we'll stick with this from now on ) were partially right and I was too generous in my original position.
Not about the gatekeeping itself, no, that's still wrong. But about the tendency: AI does make it easier to produce low-understanding code. Not because AI is bad, but because removing friction is literally what AI does, and friction is what builds understanding.
The answer isn't "use less AI" or "never use AI." It's "if you use AI to skip engagement with the problem, whether that's the code, the failure modes, or the maintenance, you build worse systems.
That applies to humans too but humans have an inherent friction cost, we get bored, we hate typing, we make mistakes. That friction is annoying, but it forces us to stay engaged.
So What Changed?
Nothing about the original argument was wrong. But I was treating "understanding + accountability" as if they were just checkboxes you could verify at the end.
They're not.
Understanding isn't a property you can inspect, it's a process and that process requires friction. Real engagement with the problem. Mistakes you learn from.
The reason I'm prouder of handwritten code is because I earned the understanding in a way that's harder to fake.
Does that mean every line should be handwritten? No. Boilerplate is boilerplate. Some friction is just noise.
But the core logic? The architecture decisions? The places where "understanding this code" actually matters? Yeah, I want to have fought for that.
And I want the same from anyone shipping code that matters.
The Uncomfortable Middle Ground
Here's where I land:
To the critics: You're using the wrong filter. "AI or no AI" doesn't tell you anything but you're accidentally right that there's something real to be concerned about, it's the laziness that AI enables, not the tool itself.
To AI enthusiasts: Yeah, your tooling is amazing. But it's worth asking whether you're using it to think better or think less. Those feel the same until you try to maintain the code six months later.
To myself: The pride I feel in handwritten code isn't ego, It's legitimate. every line that I typed out isn't some sort of magical unicorn mythological Godly line but it's the friction that comes with it, the learning I build that comes with it.
Sometimes the answer requires admitting you skipped and sometimes that's fine for routine work. But if you're building something that matters, you should want to have fought for it.
Choose Where You Fight
I still write a lot of code by hand. I still use AI every day. Those aren't contradictions anymore.
The original article was right: origin doesn't determine quality. What it missed is that understanding isn't a checkbox you tick at the end. It's something you earn through friction. Handwriting tends to create that friction. AI tends to remove it. Neither is inherently good or bad. Both are choices about how you engage with the problem.
So I'm not asking "Did you use AI?" anymore.
I'm asking: Did you fight for the parts that matter?
And if the tool answered that for you, you'll find out the next time something breaks.
Top comments (24)
Great angle! When I first started using AI for coding, I mostly used chatbots. They would generate code snippets, which was incredibly useful, especially when I was experimenting with technologies I didn't know yet. But there was still some friction. I had to read the code, understand it, figure out why something didn't work, and make it fit my project.
Coding agents have reduced that friction even further.
Of course, in production you still need to think, review the code, secure it, and understand the bigger picture. But I think this is especially dangerous for people who are just learning to code or trying to break into the industry. The less friction there is, the easier it becomes to mistake "getting something working" for actually understanding what's happening.
100%. Coding agents take friction reduction to another level entirely.
It's so easy to mistake a working feature for actual understanding, especially when starting out.
Finding ways to intentionally reintroduce that curiosity and critical review process is gonna be key, especially for newcomers!
Thank you for taking the idea further than I stated it. One correction, and it
runs in your favor.
You framed my case as: there are parts of the system I couldn't explain
line-by-line, so I reach for demonstrated behavior instead. True, but the
ordering is off. The parts I can explain line-by-line are not the parts that
have broken. Nearly all my failures were in code I understood perfectly and had
never run under deliberate failure — a check whose pattern silently matched
nothing, a verification that reported success for a page that was 404, a guard
whose test passed identically whether the guard was present or not.
Understanding caught none of those. It couldn't. They were all behavior at a
boundary I had never visited.
So it isn't a ladder where demonstration substitutes for understanding further
down. They catch different classes, and I'd argue the second class is the more
dangerous one.
On friction: I don't think AI removes it. It relocates it. I skip the friction
of syntax and walls and I inherit the friction of "how would I know if this were
wrong?" — which is where every drill I run came from. The difference is that the
first kind announces itself. It stops you, it won't compile, the wall is right
there. The second kind is silent, and silent friction can be skipped without you
ever noticing you skipped it. That might be the sharper form of your point: not
that AI removes friction, but that it removes the friction which interrupts you
and leaves the friction you have to go looking for.
And on @komo's maintenance receipts, which I agree is the best line in the
thread — receipts also accumulate for someone who keeps fixing things they never
understood. What makes one into evidence is whether the failure was predicted
before it arrived. A receipt for a fire you forecast is a different document
from a receipt for a fire you attended.
I really like the distinction made here.
"AI doesn't remove friction, it relocates it" is probably a much more precise way to put it than my original wording. it's easy to skip questions that never force themselves into your path.
And yes, understanding and demonstrated behavior aren't substitutes for each other. They catch different failure modes. I think the takeaway for me is that neither is sufficient on its own; confidence comes from combining both. Thanks for taking the time to expand on it. There's a lot to think about here.
One refinement on "combining both," and then I'll leave it.
They don't combine additively. Understanding is what tells you which boundaries
are worth breaking — without it you drill at random and most drills are wasted.
Demonstration tells you what actually happens there. So it's less two votes you
add up, more that one aims the other. Which is exactly why my version is slower:
I have to find the boundary by getting hurt at it first.
I like it.
The only thing I'd add is that I don't think it always flows in one direction. Sometimes reality surprises us in ways our mental model never saw coming. You hit a boundary you didn't even know existed, and that's what changes your understanding.
So I guess I see it more as a loop than a sequence. Understanding tells us where to look, demonstration tells us whether we were right, and when it isn't, we come away with a better mental model.
I think that's one of the reasons software engineering stays interesting. No matter how much you understand, reality still has a way of teaching you something new.
Agreed, and the loop framing is better than mine.
One thing I'd add about the surprise step: reality has never actually told me
anything directly. Every boundary I found by accident, I found because two
witnesses disagreed. The API said a comment existed; the rendered page said it
didn't. My log said one thing; my memory said another. A control group said one
thing; the live population said another. Reality was being just as surprising
before those pairs existed — it just had no way to reach me.
So the practical form of your loop, for me, is: keep two things running that
could contradict each other. Otherwise the surprise step never fires, and you
mistake that for there being no surprises.
This is pretty close to how I feel. When I first started using AI to code, I sometimes felt guilty because I couldn’t fully explain everything it generated. But after experiencing how much faster AI coding can be, writing myself also made me feel frustratingly slow.
So I keep going back and forth. There are still times when I disagree with the architecture AI suggests and choose an approach I think is better. But this process could also become part of how I learn. Maybe the goal isn’t to avoid AI or accept everything it writes, but to stay involved enough to know when to trust it and when to push back.
Haha, yeah AI raised the bar high when it comes to coding speed. during the era when we manually write alot of the things ourselves, we often overlook the learning that comes with the friction.
I'm gonna be a little nostalgic here, it was a great time haha.
The "maintenance receipts" line is the sharpest part of this follow-up. When an agent drafts most of a change, the honest ownership test is not who typed the first version. It is whether someone can still defend the weird edge cases weeks later when production pings them. Treating the actual diff as that quiz helps: if you cannot explain a hunk without leaning on the agent summary, that hunk is not ready. The friction you describe is exactly what careful review is supposed to reintroduce when generation removed it from the write path.
right? This also reminds me of the idea of "test/project contamination."
If you let AI come up with the edge cases and then also write the tests for them, the tests will almost always pass because they're validating the same assumptions they were generated from.
The harder part is the edge case nobody thought of. The one that only shows up in production because reality doesn't care about our mental models. Those are usually the ones that come from experience - either your own or someone else's.
This is very well written honestly. I couldn't have said it better myself. I was engaged all throughout and I completely agree with your points. It doesn't matter if you used AI or not, what matters is if you can defend it, if you can own it, if you understand it. That alone tells me if he/she has gone through the "friction" to get this concrete understanding of their code. If they can't fully explain the code then are they even worthy to be called the author or just a copy paste bot for AI.
Thank you! I really appreciate that.
I think "own it" is the key phrase here. Whether the code came from you, AI, stack overflow or a teammate matters a lot less than whether you can explain the trade-offs, defend the decisions, and know where it might fail.
That's what separates using AI as a tool from letting it do the thinking for you.
Btw, I came because I saw Michael Jackson on a computer confused. I left with better AI understanding.
I was trying to find a suitable pic for this article cover, couldn't find one but i remember i had generated a pic of Michael pushing node modules to git and i was like, good enough, ill put this haha
part of me wanting to put this also comes from my love for Mike!
I really like the evolution of your argument. One thought I’d add is that I don’t think friction itself creates understanding—feedback does. Handwriting code often creates rich feedback loops because every decision is yours, but AI can also provide them if it’s used as a sparring partner instead of an autocomplete engine. Conversely, plenty of handwritten code is produced on autopilot with very little learning. Maybe the real question isn’t “Did you fight for it?” but “Did your process continuously challenge your assumptions?” That’s what tends to produce lasting understanding.
I'd say friction and feedback are inseparable. Friction is what forces you to encounter feedback; feedback is what you extract from the friction. Handwriting tends to lock them together, you can't avoid the feedback because you created every line. AI lets you decouple them, which is powerful if you're intentional, but dangerous if you're not.
I like that distinction. I’d still argue the two can be decoupled. A well-designed feedback loop—tests, chaos engineering, observability, code reviews, even production incidents—can challenge assumptions without requiring much implementation friction. Likewise, plenty of friction produces very little learning if it’s just repetitive struggle. So maybe the invariant isn’t friction or feedback, but exposure to disconfirming evidence. The more your process forces your mental model to collide with reality, the deeper the understanding becomes, regardless of whether the code was handwritten or AI-assisted.
Mmm, the deeper principle: disconfirming evidence is what builds understanding.
But here's the pragmatic tension: handwriting forces frequent collisions with reality during development. External feedback systems require intentional design and discipline to maintain. Both can work, but one is automatic and one requires constant choice.
so I guess that's why I still prefer fighting during writing, it's the path of least resistance toward actually learning.
The point about latency and network hops is so underappreciated. Out of curiosity, what's your go-to stack or approach for running and serving these smaller fine-tuned models locally without making infrastructure maintenance a pain?
Haha, I think you might be mixing this up with another discussion. 😄 I don't think I mentioned latency, network hops, or serving local models anywhere in this article.
Happy to chat about that topic though!
One mechanism that seems to tie several of these threads together: the self-quiz idea is essentially retrieval practice, and retrieval is what separates exposure from understanding. A note you never pull back up is as good as unwritten — the honest test for any system, notes or code, is closing the file and reconstructing the idea without looking.
That's also why "maintenance receipts are much harder to fake" rings true: a bug fix is a retrieval event under real conditions. You locate the right spot and reason about why the change is correct while the system runs and other people depend on it — much harder to fake than an explanation produced with the code in front of you.
And it explains the friction effect: handwriting forces construction, while reading or curating AI output is recognition — it feels like understanding but doesn't stick, which is exactly what you described with reviewing logic perfectly and then not being able to write it from scratch. A cheap habit that works for me: once a week, take one piece of generated code you actually ship and explain it without looking at the screen. If you can't hold it in your head, compress it until you can — that friction is the learning.
I really like the angle. "Exposure vs. understanding" is a great way to frame it.
I also like the part where you said "close the file and reconstruct the idea." I think that's a much stronger test than just reading through code and feeling like it makes sense.
I also like your point about maintenance being retrieval under real conditions. Not just recalling the code, you have to recall the reasoning behind it while new constraints are being introduced. That's probably why those experiences stick with us so much.
Thanks for sharing this. I hadn't connected it to retrieval practice before, but it fits the article really well.
Spent hours in code reviews that felt like a maze. Your post resonates with the frustration of waiting on feedback that's either too late or too vague. It's a reminder that clear, actionable feedback is the grease that keeps the development process moving smoothly.