DEV Community

Cover image for Understanding Over Origin: The Missing Friction

Understanding Over Origin: The Missing Friction

Adam - The Developer ✨ on August 04, 2026

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...
Collapse
 
sylwia-lask profile image
Sylwia Laskowska

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.

Collapse
 
adamthedeveloper profile image
Adam - The Developer ✨

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!

Collapse
 
avp9nexus profile image
avp9-nexus

Both halves of an answer are already in this thread, and I think they lock together tighter than the article lets them.

Your position, that friction builds understanding and understanding is what you verify, holds right up to the point where you try to verify with it. I operate an agent system that commits value on a test network, and I keep something adjacent to what @komo called maintenance receipts: a dated register of every error, recording what caught each one. Not what the error was. What caught it.

Twenty-six entries in, classified by detector: 18 caught by an artefact (a grep, a hash, a counter, a git log), 4 by a human who happened to be reading, 3 I could not attribute, 1 by a detector my own grid has no box for, and zero by an instruction telling the system to check itself. Not one error was found by re-reading either. Every single one surfaced as an inconsistent output: a hash that diverged, a counter that did not add up, a motif missing from a log.

That does not weaken your argument, it sharpens it. Friction does build understanding. But understanding is what you come away with, not what does the catching, so it cannot be what you verify with. My rules turned out to be triggers, never judges: they cause a measurement to happen, and the artefact decides.

Which gives a test I can apply to my own rules, and it is not the one I expected. Not "what did this rule catch", because the answer is always nothing. But "what measurement did it cause". A rule that never caused one was never doing any work.

@fromzerotoship's failure modes are the ones I keep meeting, and the guard that reports green whether or not it is doing anything cost me a full working day. A broken tool does not return an error. It returns an absence, and an absence reads exactly like a clean result. Seven instruments lied to me that way in a single session. None caught by re-reading. All seven by a positive control: running the same check against a case I knew had to come back positive. If that one comes back empty too, the world is not clean, my instrument is mute.

The "keep two things that could contradict each other" line is the one I can put a measurement against, and the measurement is uncomfortable. My system refuses to act unless two independent chain endpoints agree on state, with the quorum recorded in a hash-chained decision log. Different operators, different URLs, different companies. Yesterday I finally asked what that quorum was actually made of: both endpoints were running the same execution client, at the same build, identical down to the commit hash. Every log line was accurate. Quorum two of two, no divergence. And it certified nothing, because the field that would have said these two witnesses are the same software did not exist. The disagreement step could not fire, and I had been reading that as there being no disagreements.

Same failure mode one level up: not a check that silently matches nothing, but a pair that silently cannot contradict. The drill is one extra call per witness per decision, asking each one what it actually is, and writing the answer next to the verdict. Distinctness measured at decision time, never inferred from the config that set them up. Two dead ends first, in case they save you the time: block height agreement discriminates nothing when a single sequencer feeds everyone, and DNS inspection says nothing when every provider sits behind the same edge network.

On the receipts distinction, which I think is the sharpest thing in this thread: by that criterion almost all of my 26 are attended fires. The two I would file as forecast are a claim in my own spec, that a hash-chained log proves no entry is missing, which an adversarial review killed before it reached any published surface, and a limit I wrote into the spec rather than leaving it to be discovered, that hashing your own file closes drift but never forgery.

Which exposes a problem in the criterion that I cannot solve. Both of those are receipts for fires that never happened. To me they are the two documents in the pile worth anything. To anyone who was not there, they are unfalsifiable claims about a past that did not occur. The attended fire at least leaves a scar someone else can inspect.

On choosing where to fight: I could never apply boilerplate-is-boilerplate twice the same way, so I stopped drawing the line on the code and drew it on the effect. Reversibility. What does this let happen that I cannot undo. Boilerplate that commits value gets the friction. Clever logic that renders a view does not.

Collapse
 
adamthedeveloper profile image
Adam - The Developer ✨

I really appreciate you taking the time to write this, there's really a lot here that I'll probably keep thinking about.

first off, I like the distinction between understanding as the thing that guides what you measure, and the measurement itself being what gives you confidence. I think that's a good refinement. Having a mental model is what tells you where to look, but without some external signal, it's very easy to convince yourself that your model is correct just because it makes sense.

next, the "2 witnesses that silently cannot contradict" example is probably my favorite part. It's basically the same problem as a test that always passes, just one level higher. You think you have independent confirmation, but both sides are actually sharing the same failure mode. Those are the bugs that are scary because everything looks healthy.

The reversibility point is interesting too. I haven't really used that as an explicit rule before, but it actually matches a lot of engineering decisions I've made. The more painful something is to undo, the more careful you should be before committing to it.

I think this whole thread keeps coming back to the same idea from different angles: confidence doesn't come from just understanding something or just testing it. It's the combination of having a model, trying to break it, and being willing to accept when reality proves you wrong.

Really enjoyed this discussion. Definitely gave me a few new ways to think about the topic beyond what I originally wrote.

Collapse
 
avp9nexus profile image
avp9-nexus

Thank you, and your rephrasing "a test that always passes, one level higher" is better than mine; it’s more concise and fits into a category that people already recognize.

A note on your conclusion, since that’s the part where I was wrong for months. "Having a model, trying to make it fail, accepting it when reality proves you wrong" describes a practice. The problem is that a practice loses its effectiveness precisely when you need it most, and you can’t realize that from within. What replaced it for me was building elements that fail in a "closed" way instead: a rejection that overwrites the previous decision rather than refusing to write a new one, a transfer that does nothing with a record whose reference cannot be resolved, a certificate that expires as soon as a file it covers changes. None of these elements require me to remain vigilant. That is precisely their value.

Which brings us to a design rule that this thread has circumvented without stating it: if you cannot place the "judge" beyond the process’s reach, make sure that bypassing it is noticeable. My checks reside in a directory tree that the agent modifies, so it can access it. What it cannot do is access it discreetly, because disabling one entry is equivalent to a change, and the next scan detects such changes. It’s less effective than a judge that no one can touch, but it costs nothing.

This also puts the case of the two witnesses into perspective. It’s not just that they shared a failure mode. It’s that the design included a step that was structurally incapable of triggering, and the system recorded an "OK" result every time it failed to trigger. An absence and an "OK" result look the same on every dashboard I’ve ever built. The only thing that distinguishes them is a command that returns a positive result when it should, tested against a case that we know is supposed to trigger the alert. 🫴

Collapse
 
fromzerotoship profile image
FromZeroToShip

The problem you say you can't solve — a forecast receipt being unfalsifiable to
anyone who wasn't there — I think your own rule test solves it. You just applied
it to rules and not to receipts.

Not "did the forecast fire," because it didn't; that's the whole complaint. Ask
what measurement it caused. A forecast that produced an instrument is
inspectable: the instrument exists, it's dated, someone else can run it against
a case they choose. A forecast that produced only a sentence is exactly the
unfalsifiable claim you're describing. That splits your two cleanly, and not in
your favour. The killed spec claim caused a deletion, which leaves nothing to
run. The forgery limit caused something only if the system now refuses to assert
what it can't check. If it doesn't, that receipt is a good sentence.

Your quorum finding sent me back to my own labels, and it's the same shape. My
seed has fourteen accounts labelled human from two provenances I described as
distinct: seven confirmed through long exchanges, seven by a weaker proxy. I
tested whether they discriminated, found they didn't, and read that as the test
being underpowered.

Wrong reading. Both provenances have a common cause — me. "Wrote to me at
length" is my judgment about who's real, and an automated account that writes at
length passes it. I inferred distinctness from how I constructed the labels
rather than measuring it at the point they were used. Two of two, no divergence,
certifying nothing.

Thank you for the two dead ends. Those saved more time than the finding did.

And reversibility over code type is the part I'm taking whole. I've been drawing
that line by what looked hard, which is why it moved every time.

Collapse
 
avp9nexus profile image
avp9-nexus

You are right that the test applies to receipts, and I applied it badly in both directions. Sorting properly, one of my two fails and three things I had not counted pass.

The failure first. The forgery limit produced a section of a spec. One correction to your reading: the two pinning fields are not older than it, they are from the same night. That does not rescue it, because they came from the pinning work and not from the limit. But your criterion was sharper than my confession. You asked whether the system now refuses to assert what it cannot check, and it does, in the only place that costs anything: the register writes whether the engine is pinned to an external value. It does not write that the engine is authentic. It cannot check that, so the field is named after the measurement rather than after the claim I would prefer to make. That is not an instrument. It is a naming discipline, and you can audit it on any published entry.

Now the ones that pass, and the first is the one I told you was a deletion. The review that killed the sentence also surfaced something I was not looking for: on an ordinary error path, a scoring failure wrote the raw model output and appended no register entry, while the chain verifier still reported the file intact. Non-exhaustive with no adversary present. That produced a named failure mode in the register, a reverse check walking from stored raw outputs back to entries, and a bench that fails when either is absent. Dated, separate from the claim, runnable against a case I did not pick.

Second, and this is the one your labels rhyme with. Three days before that entry there was no field recording what the two witnesses were. The measurement produced one, and it is now written on every decision, not on the ones where I remember to look. Your fourteen labels are the same finding on an object that shares nothing with mine, and yours is cleaner because the common cause is visible: mine hid behind two company names, yours is one person's judgment wearing two coats. The shape is not redundancy. It is that distinctness gets inferred from how the sources were assembled, and assembly is where the common cause lives.

Third, and it is the one that answers your unfalsifiability problem better than I did. The entry carries the scores, the weights and the aggregate. Anyone can take the weighted sum and check the rounding without asking me for access, and the same predicate is enforced by the verifier on every entry. That is a step past your criterion: not an instrument a third party can run, an artefact that travels with its own check. Where I can build that, the forecast receipt problem mostly dissolves. Where I cannot, your test is the right filter and I should stop calling the rest receipts.

Which leaves the nine near-misses I published last week as prevention, true and unfalsifiable exactly as you describe. Sorting them by what each caused is the honest thing to do with that list, and I expect it to be unkind.

And the probe generalises, but only if the question is asked at the point of use: not are these two different, but what could I ask them, at the moment I rely on them, that they could not both answer the same way by construction. For endpoints that was one call. For labels I do not know, and I suspect it is the harder case, because what you would interrogate is a person's judgment and it does not have a version string.

Thread Thread
 
fromzerotoship profile image
FromZeroToShip

Your (c) is a step past my criterion and I'll say so plainly: an artefact that
carries its own check is strictly better than one a third party has to be handed
an instrument to verify. Mine required someone to trust the runner. Yours doesn't
require a runner.

On the hard case you left open — a person's judgment having no version string.
I think that's right about the judgment and wrong about what you'd interrogate.
The judgment has no version. The evidence it was made from does.

My seven "confirmed human" labels record a verdict: long exchange, therefore
person. What they don't record is what I actually saw — how many rounds, whether
the exchange survived disagreement, whether they changed their own code because
of it. Store the observation instead of the conclusion and the label becomes
interrogable at point of use, because two labels resting on non-overlapping
observations can disagree, and two resting on the same observation cannot. That's
your endpoint call, one level down.

But the residue is worse than that and I only found it writing this. Even with
the evidence stored, every piece of it reached me through one channel: people
interacting with me on one platform. A farm targeting me specifically produces
long exchanges. So the honest answer to "what could I ask them that they couldn't
both answer the same way by construction" is not about my judging at all — it's
"through what channel did this evidence arrive." Both of my provenances answer
that identically, and no amount of care in the judging fixes it.

Which gives me the probe I didn't have: does this account have sustained
interaction that does not involve me. That's a witness assembled somewhere I'm
absent. I haven't built it and I don't yet know if the platform exposes enough to.

Assembly is where the common cause lives — that sentence is doing a lot of work
and I'd have taken twenty rounds to reach it.

Collapse
 
jugeni profile image
Mike Czerwinski

Friction and receipt are the same requirement measured from opposite ends. Friction is upstream: did the understanding cost you anything while you built it. Receipt is downstream: is there evidence afterward that would catch you if you were wrong. Someone can pay the full friction cost and still ship something that breaks silently because nothing downstream is watching for it, and someone can skip all the friction and still get caught the moment something goes wrong, if the system around them is built to surface that.

fromzerotoship's plant-the-defect-and-watch-the-guards-catch-it example is the interesting middle case. That is friction too, just manufactured on purpose, after the fact, instead of stumbled into while typing. You do not need to have fought the code by hand if you are willing to fight it on purpose once it is running. Same mechanism, different point on the timeline.

Which points at the actual question under both of yours: not did you struggle, not can you explain it, but is there a moment, planned or accidental, where being wrong would have been visible to you. Handwriting guarantees that moment happens early. A good failure-injection habit guarantees it keeps happening. Skipping both is the only real failure mode.

Collapse
 
adamthedeveloper profile image
Adam - The Developer ✨

Spot on. It’s really not just about whether you struggled during the build, but whether there's a point in the system where being wrong actually catches up with you. Skipping both the upfront friction and downstream checks is how teams end up in real trouble.

Collapse
 
fromzerotoship profile image
FromZeroToShip

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.

Collapse
 
adamthedeveloper profile image
Adam - The Developer ✨

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.

Collapse
 
fromzerotoship profile image
FromZeroToShip

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.

Thread Thread
 
adamthedeveloper profile image
Adam - The Developer ✨

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.

Thread Thread
 
fromzerotoship profile image
FromZeroToShip

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.

Thread Thread
 
adamthedeveloper profile image
Adam - The Developer ✨

I think that's why independent verification is so valuable. Tests, logs, metrics, user reports, control groups... they're all different witnesses. When they agree, confidence grows. When they don't, that's where the interesting learning happens.

Thanks for the discussion. You've given me a better way to think about something I'd mostly been describing intuitively.

Collapse
 
sarahpan profile image
Sarah Pan

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.

Collapse
 
adamthedeveloper profile image
Adam - The Developer ✨

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.

Collapse
 
xm_dev_2026 profile image
Xiao Man

'Did you fight for the parts that matter' is the right question, and it has a cheap operational version: watch who touches the code during an incident. The person who fought for it navigates by memory — they know which module to open before they've read a line of the traceback. The person who supervised its birth starts the same way a stranger would: by reading. Both can fix the bug, but only one of them already had the map.

Collapse
 
adamthedeveloper profile image
Adam - The Developer ✨

Orrrr both have the map, but only one knows where the missing, ripped-out piece actually went.

Collapse
 
xm_dev_2026 profile image
Xiao Man

And the one who knows will swear under oath the map was already torn when they inherited it. The metaphor holds up uncomfortably well.

Collapse
 
unitbuilds profile image
UnitBuilds

Just read the summary comment if you dont want to read me rambling 😂

But is it an AI problem, or is it a systemic problem? Idk if you've ever had to review a junior's work, but when you do, most of what they write, you can just skip through as boilerplate, but then the last 10% is the critical parts that need careful scrutiny. AI works the same. You as the developer are expected to have senior level intelligence when using it, because it's on you to correct the AI. That's a fundamental skill that isnt only gained from hand-writing it. It's a skill learned from trial and error, whether that's at your hand, AI, or a junior. That critical distinction shifts the narrative from "Do you understand it", to "did you do due diligence in reviewing it". Whether it's AI, a junior or just you overworked and getting a bit burnt out, everyone makes mistakes, whether that's writing it, or reviewing it, that's the barrier between the ideal world of 'it just works' and 'it's meant to work, but it doesnt, why doesnt it work?' And that's the mental shift that AI brings in full-force. AI can do everything for you, from scratch to runtime, but if you consider it done because it runs, then you're the problem, not the AI, because your reviewing is cut short of what it should be. AI makes it easy to write highly complex systems that work and dont work, but people keep focusing on the 'AI slop' aspect and neglect the fact that if you do YOUR job right, that flaw gets corrected before it's committed. So it's a lazy error, you save 75% of your time writing code, but you consider that a productivity boost, because you can generate 4x the code in the same amount of time, when that's exactly why hiring 100 juniors does not equal 25 seniors. Scaled production requires scaled testing and QA, you save 75% of your time, but that's if your DoD is code compiles, what it should be is code is validated. So instead of saving 75%, save 50% instead and use that 25% to actually properly go through the AI generated code. If it saves you from even a single production error, it's worth it. And it's not 'it takes twice as long', it's still 50% faster than hand rolling it yourself. In finance, there's a saying 'there's no easy money', programming is the same, if you work hard for it, it pays off, it works, but if you see AI as 'easy money' or more aptly 'easy productivity', then your thinking is the fundamental problem. Just because you use a calculator instead of an abacus for your accounting, doesnt mean you can suddenly stop verifying your numbers. Same goes with computer vs calculator, having your figures in a spreadsheet doesnt prevent you from typing an extra 0, you still need to run the validation and verify your figures. If your expected outcome is 10k, and you get 10k, that says nothing. Add a zero, miss a zero, suddenly it balances, but it's wrong, despite looking right and passing the basic totals test. It's the practice of going through your figures again, making sure everything is correct that takes time, but is what prevents that 1 in a thousand error you know you'll make. So if an accountant has to scrutinize their deterministic formulas with such rigor, why would you think a programmer using AI is any different?

At some level of abstraction, every programmer's understanding falls apart, if I ask you 'do you know how Console.WriteLine() works', can you explain it to me in logic gates? No, because that's out of your scope, you rely on Console.WriteLine() instead of raw assembly, same as raw assembly relies on OpCodes, which relies on registers on the processor, your understand is fundamentally limited, accept it, whether it was an easy win, or a hard-fought battle, you dont know everything and you cant possibly understand everything. All you can hope to do, is at some abstraction level, understand what was used and why it works. That's the same with AI, trust it, but validate it, before you commit, not because of AI Slop, or the search for understanding, but because due diligence is the mandate for a commit. Friction isnt what builds understanding, it was always just something that wears you out. What builds understanding, is validation. You trust Console.WriteLine() because you validated that it does indeed write a line in console, you never bothered to ask how GC is handled... You take the easy route daily, through abstraction layer ontop of abstraction layer, AI is just another layer that hasnt gotten to a deterministic state yet. If you were to decompile .Net Framework, you'll see that more than 80% of the code is dedicated to edge-cases, purely because things will break and when they break, you need some way to catch it and correct it. That's the core infrastructure you use daily, that's still bug-fixing, not because of AI slop, or human error, but because a square peg can fit in a round hole, if it's the right size and when it fits, it ships, even if it leaks.

Collapse
 
unitbuilds profile image
UnitBuilds

Summary: Friction does not = understanding. Validation does. You could write it up from OpCodes, know it end to end and it'll still break on edge-cases, that's a fact for every framework that exists. AI has it's limitations, it's essentially a glorified pattern matcher, the real problem is that a square peg can fit a round hole if the size is right, it ships, but it leaks and when you realize it leaks, you have 2 choices, patch it, or replace it entirely. We do the same with hand-written code. We write it, it works, till there's an unguarded buffer overflow and it crashes, or an unguarded circular dependency. That's not AI exclusive and that's not even an understanding problem from the fundamental sense. That's real world finding a way to break every system. Take SnackBar in Blazor, you close it, but it still persists in the VDOM, so now that corner of your screen is essentially dead, you cant click UI behind it. That's production environments breaking code in ways the developer didnt expect. If the developer had done due diligence and considered GC, that wouldnt be a problem, but you dont think about GC, do you? You just use Console.WriteLine and expect it to work, you dont even understand the underlying code, but you just assume it works? See the link? Developers push code that runs and works on the happy path, but production will break it, that's why we validate edge cases and guard against them, to make sure that a square peg cant fit a round hole, no matter the size. That's understanding, not by friction, but by the life-long journey you'll face as a developer, where it works, till it doesnt. Whether you fix it by hand, or use AI, the understanding is the same and the result is the same, the only difference is velocity. You bug-hunt an hour, the AI finds and patches it in 2 minutes. But when you bug-hunt, do you actually thoroughly validate for another 30 min? How about you use AI, then validate for 30 min? 32 min vs 1h30 min, same outcome, same understanding, almost 1/3rd the time spent. But reality is after an hour hunting, you wont spend 30 min validating... You'll consider it case closed and move on... That's the developer's reality, you have limited time for it, so use what you can to speed up the process, not to rush, but so you actually have the time left to validate.

Collapse
 
adamthedeveloper profile image
Adam - The Developer ✨

left a reply on the long version, haha

Collapse
 
adamthedeveloper profile image
Adam - The Developer ✨

I think you actually refined the point I was trying to make.

The part about "it runs" vs "it is validated" is probably the biggest thing here. Whether the code comes from AI, a junior dev, or yourself after a long day, the responsibility doesn't disappear. The code still needs someone to challenge it.

I like the calculator analogy too since tools have always removed friction. Nobody writes assembly to build a web app, and nobody questions every line of Console.WriteLine() before using it. We trust abstractions because they have been validated over time.

Where I think AI is interesting is that it removes a different kind of friction. The struggle of writing the code is gone, but the hard part was never just typing anyway. The hard part was knowing if what you wrote was actually correct.

I do still think some friction is useful though. Not all friction, obviously - nobody misses fighting with syntax errors for hours 😂. But sometimes wrestling with a problem is where you build the instincts that help you review things later.

So maybe the real shift with AI is that we move more of our time from producing code to validating decisions. The bottleneck changes, but the responsibility stays the same.

Great comment btw, lots of good points here it was worth a read haha.

Collapse
 
unitbuilds profile image
UnitBuilds

Exactly, instead of focusing your efforts on how it does it, focus on why it does it. If it's formula is e=mc^2, justify it. That's why I like functional languages, because each function has a purpose and that purpose must be clear and deterministic. If it says that's the formula, you can test that formula and see if it's infact the formula that satisfies the requirement. That's abstraction away from Code and straight back to mathematics and algorithms (funny, cuz that was where we were at binary). Code is an abstraction of a formula, if the formula is correct, the output is correct, if the formula is wrong, the output CAN be wrong, but it might at times also be right. That's the distinction. Just because it works once, doesnt mean it's correct, because to be correct, it needs to be right EVERY SINGLE TIME. That's the real shift now and while our modern programming languages abstracted it to a readable state, the next tier would be to abstract away from it, to where it's 'AI native' and we're left interpreting and validating formulas again to make sure it's right.

Collapse
 
ida_zhang_cc496f24f8c03ef profile image
Ida Zhang

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.

Collapse
 
adamthedeveloper profile image
Adam - The Developer ✨

it's the uncertainty around it. Waiting days for feedback only to get comments that don't explain the actual concern creates more friction than value.

Good reviews should help someone make a better decision, not just point out what's different. Clear feedback turns reviews from a bottleneck into a collaboration!

Collapse
 
ida_zhang_cc496f24f8c03ef profile image
Ida Zhang

"Did you fight for the parts that matter?"

One thing that struck me is that even when you do carefully review AI-generated code, the review itself is a real friction point. We ran a scan of 270 real AI-generated projects and found 57.8% had at least one real bug, and 11% had security issues like hardcoded keys or XSS — and they all looked clean on the surface.

We ended up building a tool called CodeVouch that does a multi-pass deep review on AI-written code (basically what a senior engineer would do, but faster) and cross-validates every finding before surfacing it. It doesn't replace the human fight — it just filters out the noise so you can spend your energy on architecture and design decisions.

Curious if something like this would help with the "maintenance receipt" problem @komo mentioned — or does it just add another layer of friction?

Collapse
 
hoseinmdev profile image
Hosein Mahmoudi

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?

Collapse
 
adamthedeveloper profile image
Adam - The Developer ✨

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!

Collapse
 
eduzsh profile image
Edu Peralta

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.

Collapse
 
adamthedeveloper profile image
Adam - The Developer ✨

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.

Collapse
 
merbayerp profile image
Mustafa ERBAY

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.

Collapse
 
adamthedeveloper profile image
Adam - The Developer ✨

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.

Collapse
 
merbayerp profile image
Mustafa ERBAY

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.

Thread Thread
 
adamthedeveloper profile image
Adam - The Developer ✨

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.

Collapse
 
codingwithjiro profile image
Elmar Chavez

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.

Collapse
 
adamthedeveloper profile image
Adam - The Developer ✨

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.

Collapse
 
codingwithjiro profile image
Elmar Chavez

Btw, I came because I saw Michael Jackson on a computer confused. I left with better AI understanding.

Collapse
 
adamthedeveloper profile image
Adam - The Developer ✨ • Edited

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!

Collapse
 
secondbrainstarter profile image
Second Brain Starter

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.

Collapse
 
adamthedeveloper profile image
Adam - The Developer ✨

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.

Collapse
 
secondbrainstarter profile image
Second Brain Starter

Appreciate the thoughtful reply — and your point about the new constraints is exactly the mechanism I find most interesting. Recognition only needs the old context; reconstruction has to rebuild the reasoning while the constraints have already moved. That is also why the habit works best on real work: when the system changes under you, the retrieval is forced, not self-imposed. Nice that it landed as retrieval practice — that is the research-backed name for what you described.

Thread Thread
 
adamthedeveloper profile image
Adam - The Developer ✨

It took a lot of thinking after my previous article to write this follow-up. There are some great people here who will point out your old habits out loud (like mine), deepen what friction really means, and challenge how we think about building software in this AI era.

Collapse
 
degenspace profile image
degenspace

I love how this article flips the script on 'just use AI'. The missing friction isn't just about nostalgia — it's about the thinking that happens when you write code by hand. I noticed the same thing when I switched from Copilot to manual coding for a week. The bugs I caught early because I actually understood the logic? That's the friction we're losing. It's not about being faster, it's about being better.

Collapse
 
adamthedeveloper profile image
Adam - The Developer ✨

When I first had an AI generating code for me, it was like discovering a hidden, forbidden gem. I thought this is gonna be the greatest tool ever invented.

Well, I overstated that for a bit and It also hit me pretty hard when I realized I couldnt write some of the code I used to write haha.

Friction matters!

Collapse
 
mudassirworks profile image
Mudassir Khan

the "maintenance receipts are much harder to fake" observation is the part that actually changes how you evaluate AI contribution. we started treating PR authorship as the wrong signal — anyone can explain generated code in a review. but who picks up the shift at 2am when that code breaks, who debugs the third order effect? that's the maintenance receipt.

the "quiz yourself on your repository" idea is the practical version. not gatekeeping, more like: can you reason about the decision that shaped this code, not just what it does?

do you think the friction gap is mostly a hiring evaluation problem or a team culture problem, or is that the same root issue?

Collapse
 
adamthedeveloper profile image
Adam - The Developer ✨

I think it comes down to team culture. Most orgs don't actually care if there’s friction involved or not, it just depends on how that specific team approaches learning.

  • Traditional teams push for upstream friction because they believe struggling builds real understanding.

  • Laid-back teams take a middle ground: use whatever tools you want, as long as you can explain the generated code.

And then there’s the worst category: purely "results-oriented" teams that don't give a flying crap about friction and happily ship slop under the excuse of move-fast delivery.

Collapse
 
_a4ce306f0a3e42a7d5751 profile image
balsampearshen

Current AI systems still demand human supervision—hence the need for a loop engineer. As models advance, however, human intervention is expected to diminish over time.

Collapse
 
adamthedeveloper profile image
Adam - The Developer ✨

in short, it's still a tool and tool needs supervision. I remember when digital calculators started appearing more and more and people thought they were gonna replace mathematicians, haha. funny.

Collapse
 
innovationsiyu profile image
Siyu

The maintenance receipts idea is the strongest point in this whole debate. Origin is a claim, but behaviour under pressure is evidence. I built Opportunity Skill around the same asymmetry. When an agent records impressions about its user, the strongest signals are never the skills a person claims. They are rejection patterns and the things the person consistently insists on across dozens of interactions, because those cannot be performatively curated. Friction is what makes a signal credible, whether it is code or a reputation. Understanding shows up in what survives the fight.